<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>꿈꾸는 시스템 디자이너</title>
    <link>https://here4you.tistory.com/</link>
    <description>System Designer
- Web Service
- IoT Service</description>
    <language>ko</language>
    <pubDate>Mon, 18 May 2026 16:16:44 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>독행소년</managingEditor>
    <image>
      <title>꿈꾸는 시스템 디자이너</title>
      <url>https://tistory1.daumcdn.net/tistory/373170/attach/1a566b8ccb6f49b6b23ccd09f0b274d2</url>
      <link>https://here4you.tistory.com</link>
    </image>
    <item>
      <title>Next.js 팁</title>
      <link>https://here4you.tistory.com/283</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1.비주얼코드에 Eslint와 Prettier 적용하기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필요한 확장&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- ESLint&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Prettier&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트에 dependency 설치&lt;/p&gt;
&lt;pre id=&quot;code_1634110451277&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ npm i -D eslint eslint-config-prettier eslint-plugin-prettier prettier&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 스타일드 컴포넌트 사용하기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적인 CSS-in-JS 라이브러리&lt;/p&gt;
&lt;pre id=&quot;code_1634112188264&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  &quot;dependencies&quot;: {
    &quot;styled-components&quot;: &quot;^5.3.1&quot;,
  },
  &quot;devDependencies&quot;: {
    &quot;@types/styled-components&quot;: &quot;^5.1.15&quot;,
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Development/JS</category>
      <author>독행소년</author>
      <guid isPermaLink="true">https://here4you.tistory.com/283</guid>
      <comments>https://here4you.tistory.com/283#entry283comment</comments>
      <pubDate>Wed, 13 Oct 2021 16:34:28 +0900</pubDate>
    </item>
    <item>
      <title>[NestJS] 첫 번째 프로젝트</title>
      <link>https://here4you.tistory.com/282</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;프로젝트 생성&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;first-nest란 NestJS 프로젝트를 생성한다.&lt;/p&gt;
&lt;pre id=&quot;code_1633412745530&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;root@08a895abeaa2:~# nest new first-nest
⚡  We will scaffold your app in a few seconds..

CREATE first-nest/.eslintrc.js (631 bytes)
CREATE first-nest/.prettierrc (51 bytes)
CREATE first-nest/README.md (3339 bytes)
CREATE first-nest/nest-cli.json (64 bytes)
CREATE first-nest/package.json (1966 bytes)
CREATE first-nest/tsconfig.build.json (97 bytes)
CREATE first-nest/tsconfig.json (546 bytes)
CREATE first-nest/src/app.controller.spec.ts (617 bytes)
CREATE first-nest/src/app.controller.ts (274 bytes)
CREATE first-nest/src/app.module.ts (249 bytes)
CREATE first-nest/src/app.service.ts (142 bytes)
CREATE first-nest/src/main.ts (208 bytes)
CREATE first-nest/test/app.e2e-spec.ts (630 bytes)
CREATE first-nest/test/jest-e2e.json (183 bytes)

? Which package manager would you ❤️  to use? npm
✔ Installation in progress... ☕

   Successfully created project first-nest
   Get started with the following commands:

$ cd first-nest
$ npm run start


Failed to execute command: git init
Git repository has not been initialized
                                                                                                                              
                                                                                                               Thanks for installing Nest  
                                                                                                      Please consider donating to our open collective
                                                                                                             to help us maintain this package.
                                                                                                                              
                                                                                                                              
                                                                                                       Donate: https://opencollective.com/nest
                                                                                                                              
root@08a895abeaa2:~#&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;프로젝트 실행&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 디렉토리로 진입하여 프로젝트를 실행한다.&lt;/p&gt;
&lt;pre id=&quot;code_1633412872427&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;root@08a895abeaa2:~# cd first-nest/
root@08a895abeaa2:~/first-nest# ls
README.md  nest-cli.json  node_modules  package-lock.json  package.json  src  test  tsconfig.build.json  tsconfig.json
root@08a895abeaa2:~/first-nest# npm run start

&amp;gt; first-nest@0.0.1 start /root/first-nest
&amp;gt; nest start

[Nest] 31184  - 10/05/2021, 2:47:29 PM     LOG [NestFactory] Starting Nest application...
[Nest] 31184  - 10/05/2021, 2:47:29 PM     LOG [InstanceLoader] AppModule dependencies initialized +22ms
[Nest] 31184  - 10/05/2021, 2:47:29 PM     LOG [RoutesResolver] AppController {/}: +4ms
[Nest] 31184  - 10/05/2021, 2:47:29 PM     LOG [RouterExplorer] Mapped {/, GET} route +3ms
[Nest] 31184  - 10/05/2021, 2:47:29 PM     LOG [NestApplication] Nest application successfully started +2ms&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저로 서버에 접근하여 서버 동작여부를 확인한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;631&quot; data-origin-height=&quot;207&quot; data-filename=&quot;이미지 1.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KVuSP/btrgOuL8Dkb/wvWmaNiB6SiXxtq0YRUKe0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KVuSP/btrgOuL8Dkb/wvWmaNiB6SiXxtq0YRUKe0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KVuSP/btrgOuL8Dkb/wvWmaNiB6SiXxtq0YRUKe0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKVuSP%2FbtrgOuL8Dkb%2FwvWmaNiB6SiXxtq0YRUKe0%2Fimg.png&quot; data-origin-width=&quot;631&quot; data-origin-height=&quot;207&quot; data-filename=&quot;이미지 1.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;리소스 추가&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로운 리소스 cats를 추가한다.&lt;/p&gt;
&lt;pre id=&quot;code_1633413187780&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;root@08a895abeaa2:~/first-nest# ls
README.md  dist  nest-cli.json  node_modules  package-lock.json  package.json  src  test  tsconfig.build.json  tsconfig.json
root@08a895abeaa2:~/first-nest# nest g res cats
? What transport layer do you use? REST API
? Would you like to generate CRUD entry points? Yes
CREATE src/cats/cats.controller.spec.ts (556 bytes)
CREATE src/cats/cats.controller.ts (873 bytes)
CREATE src/cats/cats.module.ts (240 bytes)
CREATE src/cats/cats.service.spec.ts (446 bytes)
CREATE src/cats/cats.service.ts (595 bytes)
CREATE src/cats/dto/create-cat.dto.ts (29 bytes)
CREATE src/cats/dto/update-cat.dto.ts (165 bytes)
CREATE src/cats/entities/cat.entity.ts (20 bytes)
UPDATE package.json (1999 bytes)
UPDATE src/app.module.ts (308 bytes)
✔ Packages installed successfully.
root@08a895abeaa2:~/first-nest#&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소스 디렉토리에 cats란 서브 디렉토리가 생성되고 controller, service, module 관련 파일들이 생성되고, 하부 dto 디렉토리에 create와 update용 dto 파일, 그리고 entities 디렉토리에는 기본 cat 클래스의 파일이 생성되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 말하면 리소스(cats)을 생성하면 다음과 구성들이 자동 생성되고 설정된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Controller: 라우터 처리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Service: 라우터로부터 호출되는 서비스 로직&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Module: 리소스 구성 명세 및 종속성 명세&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- cat.entity.ts : 리소스의 데이터 모델 클래스 정의 파일&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- create-cat.dto.ts/update-cat.dto.ts: 리소스 데이터의 추가 및 갱신에 사용되는 클래스 정의 파일&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;개발모드로 서버 재가동&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트의 파일이 변경될 때 마다 변화를 반영하여 서버를 재가동하기 위해 프로젝트를 개발모드로 재가동 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;package.json 파일의 scripts 항목을 확인한다.&lt;/p&gt;
&lt;pre id=&quot;code_1633414336270&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; &quot;scripts&quot;: {
    &quot;prebuild&quot;: &quot;rimraf dist&quot;,
    &quot;build&quot;: &quot;nest build&quot;,
    &quot;format&quot;: &quot;prettier --write \&quot;src/**/*.ts\&quot; \&quot;test/**/*.ts\&quot;&quot;,
    &quot;start&quot;: &quot;nest start&quot;,
    &quot;start:dev&quot;: &quot;nest start --watch&quot;,
    &quot;start:debug&quot;: &quot;nest start --debug --watch&quot;,
    &quot;start:prod&quot;: &quot;node dist/main&quot;,
    &quot;lint&quot;: &quot;eslint \&quot;{src,apps,libs,test}/**/*.ts\&quot; --fix&quot;,
    &quot;test&quot;: &quot;jest&quot;,
    &quot;test:watch&quot;: &quot;jest --watch&quot;,
    &quot;test:cov&quot;: &quot;jest --coverage&quot;,
    &quot;test:debug&quot;: &quot;node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand&quot;,
    &quot;test:e2e&quot;: &quot;jest --config ./test/jest-e2e.json&quot;
  },&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발모드로 실행하기 위해서는 start:dev 옵션을 이용한다.&lt;/p&gt;
&lt;pre id=&quot;code_1633414410627&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;root@08a895abeaa2:~/first-nest# npm run start:dev

[3:13:44 PM] Starting compilation in watch mode...

[3:13:46 PM] Found 0 errors. Watching for file changes.

[Nest] 12960  - 10/05/2021, 3:13:46 PM     LOG [NestFactory] Starting Nest application...
[Nest] 12960  - 10/05/2021, 3:13:46 PM     LOG [InstanceLoader] AppModule dependencies initialized +22ms
[Nest] 12960  - 10/05/2021, 3:13:46 PM     LOG [InstanceLoader] CatsModule dependencies initialized +0ms
[Nest] 12960  - 10/05/2021, 3:13:46 PM     LOG [RoutesResolver] AppController {/}: +4ms
[Nest] 12960  - 10/05/2021, 3:13:46 PM     LOG [RouterExplorer] Mapped {/, GET} route +5ms
[Nest] 12960  - 10/05/2021, 3:13:46 PM     LOG [RoutesResolver] CatsController {/cats}: +0ms
[Nest] 12960  - 10/05/2021, 3:13:46 PM     LOG [RouterExplorer] Mapped {/cats, POST} route +1ms
[Nest] 12960  - 10/05/2021, 3:13:46 PM     LOG [RouterExplorer] Mapped {/cats, GET} route +1ms
[Nest] 12960  - 10/05/2021, 3:13:46 PM     LOG [RouterExplorer] Mapped {/cats/:id, GET} route +2ms
[Nest] 12960  - 10/05/2021, 3:13:46 PM     LOG [RouterExplorer] Mapped {/cats/:id, PATCH} route +1ms
[Nest] 12960  - 10/05/2021, 3:13:46 PM     LOG [RouterExplorer] Mapped {/cats/:id, DELETE} route +1ms
[Nest] 12960  - 10/05/2021, 3:13:46 PM     LOG [NestApplication] Nest application successfully started +2ms&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 파일을 저장할 때 마다 파일의 변화를 감지하여 서버가 재시작된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;리소스 접근&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cats 리소스에 접근한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 cat/를 GET으로 접근한 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;662&quot; data-origin-height=&quot;383&quot; data-filename=&quot;이미지 2.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mdZ49/btrgQAlakJ3/YK4YDPxj7gsSobbKDKH7KK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mdZ49/btrgQAlakJ3/YK4YDPxj7gsSobbKDKH7KK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mdZ49/btrgQAlakJ3/YK4YDPxj7gsSobbKDKH7KK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmdZ49%2FbtrgQAlakJ3%2FYK4YDPxj7gsSobbKDKH7KK%2Fimg.png&quot; data-origin-width=&quot;662&quot; data-origin-height=&quot;383&quot; data-filename=&quot;이미지 2.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 cat/111을 GET으로 접근한 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;662&quot; data-origin-height=&quot;383&quot; data-filename=&quot;이미지 3.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eb797a/btrgVTcQ9BW/egVPhFFsdNgWzb7ZTCLsHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eb797a/btrgVTcQ9BW/egVPhFFsdNgWzb7ZTCLsHK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eb797a/btrgVTcQ9BW/egVPhFFsdNgWzb7ZTCLsHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Feb797a%2FbtrgVTcQ9BW%2FegVPhFFsdNgWzb7ZTCLsHK%2Fimg.png&quot; data-origin-width=&quot;662&quot; data-origin-height=&quot;383&quot; data-filename=&quot;이미지 3.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cats.controller.ts 파일을 확인하자.&lt;/p&gt;
&lt;pre id=&quot;code_1633414726305&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common';
import { CatsService } from './cats.service';
import { CreateCatDto } from './dto/create-cat.dto';
import { UpdateCatDto } from './dto/update-cat.dto';

@Controller('cats')
export class CatsController {
  constructor(private readonly catsService: CatsService) {}

  @Post()
  create(@Body() createCatDto: CreateCatDto) {
    return this.catsService.create(createCatDto);
  }

  @Get()
  findAll() {
    return this.catsService.findAll();
  }

  @Get(':id')
  findOne(@Param('id') id: string) {
    return this.catsService.findOne(+id);
  }

  @Patch(':id')
  update(@Param('id') id: string, @Body() updateCatDto: UpdateCatDto) {
    return this.catsService.update(+id, updateCatDto);
  }

  @Delete(':id')
  remove(@Param('id') id: string) {
    return this.catsService.remove(+id);
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째 그림의 리소스 접근은 GET 방식으로 컨트롤러의 findAll 메소드로 전달되고, 두 번째 그림의 리스소 접근은 findOne 메소드로 전달된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Patch나 Delete 메소드는 브라우저의 URL창을 이용해서는 호출할 수 없으므로 insomnia와 같은 프로그램을 이용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://insomnia.rest/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://insomnia.rest/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1633414952545&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;The API Design Platform and API Client&quot; data-og-description=&quot;Leading Open Source API Client, and Collaborative API Design Platform for REST, SOAP, GraphQL, and GRPC&quot; data-og-host=&quot;insomnia.rest&quot; data-og-source-url=&quot;https://insomnia.rest/&quot; data-og-url=&quot;https://insomnia.rest/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cb1v5r/hyLRkcTCoU/ynWtQrhkL31Z8zRFgpOQ70/img.png?width=1200&amp;amp;height=628&amp;amp;face=0_0_1200_628&quot;&gt;&lt;a href=&quot;https://insomnia.rest/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://insomnia.rest/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cb1v5r/hyLRkcTCoU/ynWtQrhkL31Z8zRFgpOQ70/img.png?width=1200&amp;amp;height=628&amp;amp;face=0_0_1200_628');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;The API Design Platform and API Client&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Leading Open Source API Client, and Collaborative API Design Platform for REST, SOAP, GraphQL, and GRPC&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;insomnia.rest&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Development/JS</category>
      <author>독행소년</author>
      <guid isPermaLink="true">https://here4you.tistory.com/282</guid>
      <comments>https://here4you.tistory.com/282#entry282comment</comments>
      <pubDate>Tue, 5 Oct 2021 15:24:44 +0900</pubDate>
    </item>
    <item>
      <title>React 개발 환경 구성</title>
      <link>https://here4you.tistory.com/281</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;1. npm 설치&lt;/p&gt;
&lt;pre id=&quot;code_1632927148566&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ sudo apt-get install npm&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. node 설치&lt;/p&gt;
&lt;pre id=&quot;code_1632927165890&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ sudo npm i -g n
$ sudo n 14.17.6&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. npx 설치&lt;/p&gt;
&lt;pre id=&quot;code_1632927260749&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ sudo npm i npx -g&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. create-react-app 설치&lt;/p&gt;
&lt;pre id=&quot;code_1632927321962&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ sudo npm i create-react-app -g&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. create-react-app을 통해 react 프로젝트(movie_app) 생성&lt;/p&gt;
&lt;pre id=&quot;code_1632927497009&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ npx create-react-app movie_app&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. 서버 실행&lt;/p&gt;
&lt;pre id=&quot;code_1632928333769&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ cd movie_app/
$ npm start&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버 실행 후 브라우저를 통해 서버에 접속해 보자&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;636&quot; data-origin-height=&quot;463&quot; data-filename=&quot;이미지 1.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/brwWdF/btrgpMkXT0X/9U7A7Qu39tjL3xekypYNeK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/brwWdF/btrgpMkXT0X/9U7A7Qu39tjL3xekypYNeK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/brwWdF/btrgpMkXT0X/9U7A7Qu39tjL3xekypYNeK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbrwWdF%2FbtrgpMkXT0X%2F9U7A7Qu39tjL3xekypYNeK%2Fimg.png&quot; data-origin-width=&quot;636&quot; data-origin-height=&quot;463&quot; data-filename=&quot;이미지 1.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Development/JS</category>
      <author>독행소년</author>
      <guid isPermaLink="true">https://here4you.tistory.com/281</guid>
      <comments>https://here4you.tistory.com/281#entry281comment</comments>
      <pubDate>Thu, 30 Sep 2021 00:14:06 +0900</pubDate>
    </item>
    <item>
      <title>NestJS 개발에 유용한 npm 패키지들</title>
      <link>https://here4you.tistory.com/280</link>
      <description>&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;1. mapped-types&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;DTO 변환을 도와줌. 예를 들어 create용 DTO 클래스를 상속해서 update용 DTO 클래스를 선언할&amp;nbsp; 때 사용&lt;/p&gt;
&lt;pre id=&quot;code_1632707110425&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ npm i @nestjs/mapped-types&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;2. class-validator class-transformer&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;DTO 유효성 검증 및 형변환 지원. main.ts에 pipe를 등록하여 사용&lt;/p&gt;
&lt;pre id=&quot;code_1632710240929&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ npm i class-validator class-transformer&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;* pipi란?&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;node.js의 미들웨어와 비슷한 개념으로 하나의 트랜잭션이 이루어질 때 특정 순서에 주입시켜서 정해진 로직을 수행할 수 있도록 함&lt;/p&gt;</description>
      <category>Development/JS</category>
      <author>독행소년</author>
      <guid isPermaLink="true">https://here4you.tistory.com/280</guid>
      <comments>https://here4you.tistory.com/280#entry280comment</comments>
      <pubDate>Mon, 27 Sep 2021 10:45:19 +0900</pubDate>
    </item>
    <item>
      <title>NestJS 기본 소스 구조</title>
      <link>https://here4you.tistory.com/279</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;지난 포스팅에서 NestJS의 환경을 구축하고 웹브라우저를 통해 서버에 접속하여 메시지가 출력되는 것을 확인했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스트에서는 샘플코드의 소스 구조에 대해서 살펴본다. 프로젝트의 src 디렉토리의 구성을 확인하자.&lt;/p&gt;
&lt;pre id=&quot;code_1632395937388&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ubuntu@here4you:~/cats-project/src$ ls
app.controller.spec.ts  app.controller.ts  app.module.ts  app.service.ts  main.ts&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. main.ts&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;main.ts는 프로젝트의 시작점으로 서버를 실행하는 코드를 가지고 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1632396007852&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(3000);
}
bootstrap();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. app.controller.ts&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨트롤러는 라우팅을 담당한다. 다시 말해 브라우저 등을 이용해서 서버에 접속할 때 그 접속을 처리하는 역할을 수행하다. app.controller는 본 프로젝트의 root 컨트롤러로 AppService를 생성하고, 접속 요청에 해당하는 서비스를 호출한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 소스를 보면 Get 방식의 요청이 들어올 경우 AppService의 getHello 메소드를 호출한 결과를 반환한다.&lt;/p&gt;
&lt;pre id=&quot;code_1632396095521&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  getHello(): string {
    return this.appService.getHello();
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. app.service.ts&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스는&amp;nbsp; 컨트롤러에 의해 호출되어 각 요청에 해당하는 서비스 로직을 수행하고 그 결과를 컨트롤러에게 반환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AppService는 getHello 메소드를 통해 환영 메시지를 반환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1632396389724&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { Injectable } from '@nestjs/common';

@Injectable()
export class AppService {
  getHello(): string {
    return 'Hello World!';
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. app.module.ts&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NestJS는 서버를 모듈단위로 구성할 수 있다는 장점이 있다. 이는 개발과 유지보수에서 큰 장점으로 작용하게 된다. 현재는 별도의 서브 모듈이 존재하지 않기 때문에 컨트롤러와 프로바이더만 정의되어 있다. 향후 리소스가 추가되게 되면 imports 항모에 추가될 것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1632396560295&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';

@Module({
  imports: [],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;후기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그간 node.js를 이용한 프로젝트를 진행해 오다 처음으로 NestJS를 살펴보고 있는데 어느 포스트에선가 이렇게 평가하고 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;node.js를 이용하면 제로부터 시작해서 서버를 개발하게 되고, 규모가 커지고 개발자가 늘수록 힘들어진다. 하지만 NestJS를 쓰면 체계적으로 서버를 개발할 수 있게 된다.&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 살펴보니 무슨 뜻인지 알 수 있었다. NestJS에서는 오래전 Spring의 느낌이 들었다. MVC 체계에 따라서 서버를 개발할 수 있다는게 큰 특징으로 보여진다.&lt;/p&gt;</description>
      <category>Development/JS</category>
      <author>독행소년</author>
      <guid isPermaLink="true">https://here4you.tistory.com/279</guid>
      <comments>https://here4you.tistory.com/279#entry279comment</comments>
      <pubDate>Thu, 23 Sep 2021 20:32:43 +0900</pubDate>
    </item>
    <item>
      <title>NestJS 개발 환경 구성</title>
      <link>https://here4you.tistory.com/278</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;1. npm 설치&lt;/p&gt;
&lt;pre id=&quot;code_1632387687244&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ sudo apt-get install npm&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. node 설치&lt;/p&gt;
&lt;pre id=&quot;code_1632388703338&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ sudo npm i -g n
$ sudo n 14.17.6&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. Nest CLI 설치&lt;/p&gt;
&lt;pre id=&quot;code_1632387786537&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ sudo npm i -g @nestjs/cli&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 테스트 프로젝트 생성&lt;/p&gt;
&lt;pre id=&quot;code_1632388306700&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ nest new test_project&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 테스트 서버 실행&lt;/p&gt;
&lt;pre id=&quot;code_1632388497934&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ ls
test-project
$ cd test-project/
$ npm install
$ npm run start

&amp;gt; test-project@0.0.1 start /home/ubuntu/test-project
&amp;gt; nest start

[Nest] 12797  - 09/23/2021, 9:36:46 AM     LOG [NestFactory] Starting Nest application...
[Nest] 12797  - 09/23/2021, 9:36:46 AM     LOG [InstanceLoader] AppModule dependencies initialized +30ms
[Nest] 12797  - 09/23/2021, 9:36:46 AM     LOG [RoutesResolver] AppController {/}: +6ms
[Nest] 12797  - 09/23/2021, 9:36:46 AM     LOG [RouterExplorer] Mapped {/, GET} route +4ms
[Nest] 12797  - 09/23/2021, 9:36:46 AM     LOG [NestApplication] Nest application successfully started +2ms&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. 웹브라우저로 접속 확인&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;554&quot; data-origin-height=&quot;474&quot; data-filename=&quot;이미지 1.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgY9kd/btrfPBkfSTh/NqphKl9KZXUsgk7ZrXXMlK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgY9kd/btrfPBkfSTh/NqphKl9KZXUsgk7ZrXXMlK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgY9kd/btrfPBkfSTh/NqphKl9KZXUsgk7ZrXXMlK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgY9kd%2FbtrfPBkfSTh%2FNqphKl9KZXUsgk7ZrXXMlK%2Fimg.png&quot; data-origin-width=&quot;554&quot; data-origin-height=&quot;474&quot; data-filename=&quot;이미지 1.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Development/JS</category>
      <author>독행소년</author>
      <guid isPermaLink="true">https://here4you.tistory.com/278</guid>
      <comments>https://here4you.tistory.com/278#entry278comment</comments>
      <pubDate>Thu, 23 Sep 2021 18:40:12 +0900</pubDate>
    </item>
    <item>
      <title>GetView / Obx를 통한 ListView 사용법</title>
      <link>https://here4you.tistory.com/277</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Obx를 통해 위젯의 상태값을 추적할 때, 기본적으로 단일 위젯의 상태값을 바라보게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무슨 말이냐면, ListView 위젯의 경우 children을 Obx를 래핑할 수 없다는 뜻이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 ListView의 children에 들어갈 데이터 리스트는 다음과 같이 추가하고 UI에 반영할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1626661762142&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;historyList.value.addAll(
  _payload.dataList.map((data) =&amp;gt; History.fromJson(data)).toList());
ac.historyList.refresh();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ListView의 경우 일반적인 방식으로 children을 Obx로 래핑할 수 없으며, 아래와 같이 ListView.builder를 이용하면서 이를 Obx로 래핑해야 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1626661932716&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class HistoryListView extends GetView&amp;lt;AppController&amp;gt; {
  @override
  Widget build(BuildContext context) {
    return Obx(
      () =&amp;gt; ListView.builder(
        physics: const NeverScrollableScrollPhysics(),
        shrinkWrap: true,
        itemCount: controller.historyList.value.length,
        itemBuilder: (BuildContext context, int index) {
          return Text(controller.historyList.value[index].carNumber);
        },
      ),
    );
  }
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Development/Flutter</category>
      <author>독행소년</author>
      <guid isPermaLink="true">https://here4you.tistory.com/277</guid>
      <comments>https://here4you.tistory.com/277#entry277comment</comments>
      <pubDate>Mon, 19 Jul 2021 11:33:16 +0900</pubDate>
    </item>
    <item>
      <title>GetX를 이용할 때 값이 변경되지 않는 경우</title>
      <link>https://here4you.tistory.com/276</link>
      <description>&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;GetX란 Flutter앱의 상태관리에 사용되는 패키지로 Provider와 유사한 기능을 제공한다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;Provider에 비해 보다 가벼우며 더 강력하다고 하는데 기존의 Provider 대비 보다 다양한 기능을 제공하고 있다. 그래서 복잡하기도 하다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://pub.dev/packages/get&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://pub.dev/packages/get&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1626344056639&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;get | Flutter Package&quot; data-og-description=&quot;Open screens/snackbars/dialogs without context, manage states and inject dependencies easily with GetX.&quot; data-og-host=&quot;pub.dev&quot; data-og-source-url=&quot;https://pub.dev/packages/get&quot; data-og-url=&quot;https://pub.dev/packages/get&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/baee9r/hyKTWjdn51/W37G0BJKKe7KNNow7Rq8d1/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/bcr8EG/hyKSJyZBSO/uiqlCAz2YxA7JTmNj1zho0/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/DST8r/hyKTOepZEp/PG74nVlujASiIoWkbkx4V0/img.png?width=1484&amp;amp;height=267&amp;amp;face=0_0_1484_267&quot;&gt;&lt;a href=&quot;https://pub.dev/packages/get&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://pub.dev/packages/get&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/baee9r/hyKTWjdn51/W37G0BJKKe7KNNow7Rq8d1/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/bcr8EG/hyKSJyZBSO/uiqlCAz2YxA7JTmNj1zho0/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/DST8r/hyKTOepZEp/PG74nVlujASiIoWkbkx4V0/img.png?width=1484&amp;amp;height=267&amp;amp;face=0_0_1484_267');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;get | Flutter Package&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Open screens/snackbars/dialogs without context, manage states and inject dependencies easily with GetX.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;pub.dev&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;여튼, GetX에서는 상태관리를 위해 GetxController를 상속하는 Controller 클래스를 만들고 그 클래스 안에 상태관리하고자 하는 변수나 인스턴스들을 배치해서 사용하게 된다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;Flutter의 기본 타입(Int, String 등)의 경우 Obx()를 이용해서 상태값 변경을 플랫폼이 인지하고 자동으로 그 값이 갱신되는데 직접 구현한 클래스 타입의 인스턴스의 경우 상태값 변경이 발생해도 값 변경 인지가 안되는 경우가 발생했다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;본인의 경우 데이터 모델 클래스들을 가지는 슈퍼클래스를 이용해서 각 모델의 값을 하나의 인스턴스로 관리하고자 했다. 예를 들어 다음과 같은 방식이다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;Resource&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;ㄴ Worker&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;ㄴ Device&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;ㄴ Car&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;Resource 클래스 안에 Worker, Device, Car 클래스의 인스턴스가 존재하는 형태였는데 서버에 로그인할 때마다 해당 값을 한번에 수신해서 GetController안의 Resource 인스턴스에 주입하는 방식으로 사용했고 동작도 잘 했다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;문제는 작업자의 정보를 서버에 변경하고 그 결과를 수신하여 Resource.Worker에 주입하려고 하니 Worker의 변경사항이 화면에 갱신되지 않는 문제가 발생했다. 실제 로그로 찍어보면 모델 데이터는 변경된 것이 맞으나 변경 사항이 위젯으로 전달되지 않는 것이 이유였다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;현재 파악한바로는...&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;임의의 클래스 인스턴스를 상태값으로 관리할 경우 그 내용이 변경되다고 해서 위젯에 통보되는 것이 아닌 것 같다. 상태값 인스턴스의 값이 변경되는 것만으로는 변경사실이 위젯으로 통보되지 않았다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;해당 상태값 인스턴스에 새로운 인스턴스를 생성해서 대입하는 경우에만 연결된 위젯으로 변경사실이 통보되는 것을 확인할 수 있었다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;그럼 해결방법은?&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;해결방법은 의외로 간단했는데 update함수를 이요하는 것이었다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;아래와 같이 상태값 인스턴스(signInResource)에 update함수를 호출해서 signInResource.worker값을 변경하면 연결된 위젯에 갱신이 발생하다.&lt;/p&gt;
&lt;pre id=&quot;code_1626344600125&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;signInResource.update((val) {
  val.worker = OasisWorker.fromJson(jsonDecode(_payload.data));
});&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[update: 2021-07-19]&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;ListVew를 사용하면서 갱신된 상태값이 UI에 잘 반영되지 않는 현상이 있었다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;조금 검색해보니 refresh 명령어를 통해 명시적으로 UI에 반영하는 방식이 있었다.&lt;/p&gt;
&lt;pre id=&quot;code_1626660863268&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;signInResource.value.worker = OasisWorker.fromJson(jsonDecode(_payload.data));
signInResource.refresh();&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Development/Flutter</category>
      <author>독행소년</author>
      <guid isPermaLink="true">https://here4you.tistory.com/276</guid>
      <comments>https://here4you.tistory.com/276#entry276comment</comments>
      <pubDate>Thu, 15 Jul 2021 19:25:01 +0900</pubDate>
    </item>
    <item>
      <title>ESP32를 만나기까지... (IoT 제품 개발자의 경험담)</title>
      <link>https://here4you.tistory.com/275</link>
      <description>&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;작년말 새로운 회사에 합류했다. 보직은... 음 현재 스스로를 잡부라고 부르고 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;어쨌든 컨택당시 새로운 플랫폼 사업을 시작하는 스타트업이고 개발팀장이 필요하다하여 합류하게 되었는데 어찌하다보니 IoT 장비 개발의 총괄도 맞게 되었다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;개발하고자 하는 IoT 장비가 뭔지까지는 밝히기 어려우나 간단한 IoT 장비로 BLE를 통해 스마트폰과 연동되어 동작하고 동작결과를 서버로 전송하는 장비였다. 그리고 학부 졸업반 친구들이 개발을하고 있었는데...&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;흔히 마이크로 컨트롤 보드라하면 Atmega기반에 아두이노를 쉽게 떠올리고 당시 개발자들도 아두이노를 통해 개발을 하고 있었다. 본인은 컴퓨터공학을 전공하고 전기/전자에는 경험이 전무했으며 펌웨어를 개발해본 경험도 없었다. 그러다 슬슬 장비개발에 관여할 수 밖에 없었다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;개발할 장비의 대략적인 구성은 MCU, 센서(UART), 2ch 릴레이(UART), 그리고 스마트폰과 통신하기 위한 블루투스 모듈(UART).&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;처음에는 많이 사용되는 블루투스 모듈인 HC-06과 HM-10을 이용해서 개발을하다 제품 상용화를 위해 KC 인증을 받은 BLE 모듈을 찾게되었다. 그리고 생각보다 KC 인증을 받은 BLE 모듈이 많지 않음에 좌절하게된다. 그나마 찾은 몇개의 모듈들은 어떠한 이유인지 원활한 통신이 불가능했다. 그와중에 1차 PCB 제작에 들어가고 사용도중에 BLE 모듈이 먹통이되는 문제가 발생했다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;이 시점부터 개발참여에서 개발총괄로 슬슬 포지션이 이동되게 되는데... ㅠㅠ&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;원점부터 다시 고민하고 찾아보기 시작했다. 과연 그 수많은 BLE 모듈이 탑재 제품들은 어떻게 만들어졌을까? 뭘 사용했을까?&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;혹시 아두이노(혹은 Atmega) 기반의 보드를 이용해서 IoT 장비를 개발하고자 하는 개발자가 있다면 과감히 포기하고 ESP32를 고민해보라고 말하고 싶다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;여기서 부터는 본인 개인의 경험에 의한 주장으로 꼭 답은 아니다. 판단은 각자의 몫이다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;우선 Atmega의 문제점에 대해서 정리해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;성능&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;Atmega는 8비트 기반이다. 요즘 많이 사용되는 ARM기반의 32비트 MCU와 비교하는 속도면에서 게임이되지 않는다. 더욱이 극악의 SRAM 크기다. 본인이 사용했던 Atmega2560의 경우 8Kbyte의 SRAM을 제공하는데 이게 그나마 많이 주는거다. 뭔가 계획된 절차에 따라 공정이 수행되고 그 결과를 저장하기에는 턱없이 부족한 메모리량이다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;통신모듈과 센서모듈에 UART를 연결해서 인터럽트 처리를 하고 또 1초에 한번씩 타이머를 발생시켜서 공정을 수행하고자 했으나 흔히말하는 쫑나는 현상이 발생했다. 본인의 역량부족인지 모르겠지만, 결과적으로 1초에 1번씩 응답하는 센서모듈을 타이머처럼 이용하는 괴상한 제품으로 개발할 수 밖에 없었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;크기&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;Atmega는 MCU다. 그러니 통신을 위해서는 별도의 Wi-Fi나 BLE 모듈들을 UART등으로 연결해서 이용해야 한다. 통신을 위해 물리적인 모듈이 추가되야 한다는 뜻이다. 제품 소형화에 걸림돌이 될 수 있다. 물론 이는 Atmega만의 문제는 아니다. 대부분의 제품 보드에는 MCU와 통신 모듈이 각각 별도로 존재하긴 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;비용&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;크기와 관련된 부분인데 저성능이라고 저렴한게 아니다. Atmega의 경우 꽤 비싼 칩에 속한다. 본인이 사용했던 Atmega2560의 경우 개당 1만원정도 한다. 이에 반해 ARM 기반의 32비트 MCU인 STM32 계열의 경우 성능에 따라 천차만별이긴하지만 2천원대부터 시작한다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;정리하면 Atmega는 성능도 낮고, 값도 비싸고 결과적으로 뭐하나 매력적인게 없는 MCU였다. 그런데 왜 많이 언급될까?&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;바로 아두이노 때문일 것이다. Atmega를 MCU로 탑재한 아두이노 보드는 이미 엄청난 규모의 생태계를 구축하고 있다. 대학이나 교육기관에서 IoT 장비 관련된 교구로 대부분 아두이노를 활용하고 있다. 쉽고 편해서 시작품을 신속하게 만들 수 있다. 다만 시작품 수준 이상의 제품 개발에는 어려움이 많다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;다시 원래 얘기로 돌아가서 KC 인증을 받은 BLE 모듈을 찾다가 ESP32란 모듈을 알게되었다. 그리고 결론부터 말하자면 신세계였다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;ESP32는 ESPRESSIF라는 중국 회사에서 개발하는 제품의 브랜드로 SoC와 모듈로 구분된다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;SoC의 경우 다양한 제품군이 있는데, ARM계열은 아닌 독자적인 32비트 코어(심지어 듀얼코어도 있음)에 520KB의 SRAM을 제공한다. 8KB를 제공하던 Atmega와는 상대가 안된다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;모듈의 경우도 다양한 제품군이 있는데, SoC 모듈에 플래쉬메모리와 추가 SPRAM 그리고 BLE와 Wi-Fi를 포함하고 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;다시 말하면, 그냥 하나의 모듈인데 32비트 MCU에 메모리도 넉넉하고, 파일시스템처럼 사용할 수 있는 플래쉬(기본 4MB)도 포함되고, Wi-Fi와 BLE를 지원하고 더 나아가서 GPIO도 넉넉하게 제공해서 다양한 모듈들과 연동할 수 있다. 근데 이 모듈이 개당 4천원 정도한다. 대부분의 제품들이 KC 인증을 받은 상태다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;아래 모듈이 현재 본인이 개발하고 있는 제품에 탑재되는 ESP32 모듈이다. 제품에 따라 PCB안테나가 탑재되거나 아래 사진처럼 IPEX 안테나 소켓이 달려있어 개발 제품의 특징에 맞게 선택할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot; data-filename=&quot;KakaoTalk_20210518_185419767.jpg&quot; width=&quot;480&quot; height=&quot;640&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xktqZ/btq5duwkQPx/r4NarXCLhHxkPEyxvFl8B0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xktqZ/btq5duwkQPx/r4NarXCLhHxkPEyxvFl8B0/img.jpg&quot; data-alt=&quot;ESP32-WROOM-32UE 모듈&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xktqZ/btq5duwkQPx/r4NarXCLhHxkPEyxvFl8B0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxktqZ%2Fbtq5duwkQPx%2Fr4NarXCLhHxkPEyxvFl8B0%2Fimg.jpg&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot; data-filename=&quot;KakaoTalk_20210518_185419767.jpg&quot; width=&quot;480&quot; height=&quot;640&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;ESP32-WROOM-32UE 모듈&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;아래는 ESP32 테스트 보드에 ESP32 모듈을 탑재한 것이다. 모듈 자체에 왠만한게 다 있으므로 테스트 보드에 원하는 모듈을 결착해서 외부 모듈들과 연결하고 펌웨어 개발도 가능하다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;3024&quot; data-filename=&quot;KakaoTalk_20210518_185317463.jpg&quot; width=&quot;480&quot; height=&quot;360&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TCUPh/btq5dupxlPv/xGK27d3gTHPAKPlv7tYXN1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TCUPh/btq5dupxlPv/xGK27d3gTHPAKPlv7tYXN1/img.jpg&quot; data-alt=&quot;ESP32 테스트 보드&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TCUPh/btq5dupxlPv/xGK27d3gTHPAKPlv7tYXN1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTCUPh%2Fbtq5dupxlPv%2FxGK27d3gTHPAKPlv7tYXN1%2Fimg.jpg&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;3024&quot; data-filename=&quot;KakaoTalk_20210518_185317463.jpg&quot; width=&quot;480&quot; height=&quot;360&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;ESP32 테스트 보드&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;아래는 ESP32 개발용으로 많이 사용되는 ESP32-devkitc V4 보드로 실제 본인이 꾸며서 개발중인 환경을 촬영한 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot; data-filename=&quot;KakaoTalk_20210518_185257899.jpg&quot; width=&quot;480&quot; height=&quot;640&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cEe3aS/btq5gFcrqyE/BC1QB4C2fOecr0nCwvBji0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cEe3aS/btq5gFcrqyE/BC1QB4C2fOecr0nCwvBji0/img.jpg&quot; data-alt=&quot;ESP32-devkitc v4&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cEe3aS/btq5gFcrqyE/BC1QB4C2fOecr0nCwvBji0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcEe3aS%2Fbtq5gFcrqyE%2FBC1QB4C2fOecr0nCwvBji0%2Fimg.jpg&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot; data-filename=&quot;KakaoTalk_20210518_185257899.jpg&quot; width=&quot;480&quot; height=&quot;640&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;ESP32-devkitc v4&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;IoT 장비 개발을 하고자 하면 우선 개발 보드가 필요하고, 또 IoT 장치에 걸맞게 Wi-Fi나 BLE(혹은 Class Bluetooth) 기능이 필요하다. 이러한 보드는 확보하기 위해서는 많은 비용과 시간이 발생하는데... 저 보드는 국내에서 1만원 미만으로 구입할 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;그럼 가장 큰 이슈는 개발난이도 일 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;ESP32의 경우 IDF(IoT Development Framework)라는 차체 SDK를 제공하고 있다. 꽤 성실히 지원하고 있다는 느낌이 든다. 느낌이 든다고 표현한 것은 본인도 안해봤기 때문이다. 왜냐하면 제조사에서 직접 SDK를 랩핑해서 아두이노 프레임워크에서 개발할 수 있도록 제공하고 있기 때문이다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;무슨 소리냐면... 기존의 아두이노를 개발해 본 경험이 있는 개발자라면 IDF를 이용하지 않고 기존의 아두이노 개발 환경에 ESP32 관련 라이브러리와 보드사양만 추가해서 펌웨어를 개발할 수 있다는 것이다. 즉 엄청 쉽고 빠르게 개발할 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;뭐 단점이 없지는 않다. 아두이노의 기본 API를 모두 호환하는 것은 아니다. 상당수는 아두이노 기본 API가 아닌 ESP32 API를 이용해야 한다. 그리고 정식 IDF를 이용할 때 보다 어느정도 성능저하가 발생한다. 정식 IDF의 70% 수준의 성능이라는 포스팅도 봤으나 경험을 해보지 못해 비교할 순 없었다. 다만 본인이 개발하는 제품에서는 충분히 훌륭한 성능을 제공해 주고 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;ESP32의 개발과 관련해 자료가 필요하다면 가볍게 구글링만해도 방대한 자료가 나온다. 본인이 참고한 몇몇 사이트의 링크를 첨부한다. 아두이노 개발 경험이 조금이라도 있다면 별다른 어려움 없이 쉽게 개발할 수 있을 것이다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://randomnerdtutorials.com/projects-esp32/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://randomnerdtutorials.com/projects-esp32/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1621336189630&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;130+ ESP32 Projects, Tutorials and Guides with Arduino IDE​ | Random Nerd Tutorials&quot; data-og-description=&quot;Discover all our ESP32 Guides with easy to follow step-by-step instructions. Each tutorial includes circuit schematics, source code, images and videos.&quot; data-og-host=&quot;randomnerdtutorials.com&quot; data-og-source-url=&quot;https://randomnerdtutorials.com/projects-esp32/&quot; data-og-url=&quot;https://randomnerdtutorials.com/projects-esp32/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dlNIh6/hyKgQcW5mT/QgTP8J2km8X1VkkFbhgIYk/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720,https://scrap.kakaocdn.net/dn/fosl9/hyKgJx7k6s/fpNNVNZNMjkOxWE33WtTz1/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot;&gt;&lt;a href=&quot;https://randomnerdtutorials.com/projects-esp32/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://randomnerdtutorials.com/projects-esp32/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dlNIh6/hyKgQcW5mT/QgTP8J2km8X1VkkFbhgIYk/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720,https://scrap.kakaocdn.net/dn/fosl9/hyKgJx7k6s/fpNNVNZNMjkOxWE33WtTz1/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;130+ ESP32 Projects, Tutorials and Guides with Arduino IDE​ | Random Nerd Tutorials&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Discover all our ESP32 Guides with easy to follow step-by-step instructions. Each tutorial includes circuit schematics, source code, images and videos.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;randomnerdtutorials.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://dronebotworkshop.com/esp32-intro/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://dronebotworkshop.com/esp32-intro/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1621336204445&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Getting Started with the ESP32 - Using the Arduino IDE&quot; data-og-description=&quot;Learn how to use the Espressif ESP32 microcontroller with the Arduino IDE. Step-by-step instructions for getting started with the ESP32.&quot; data-og-host=&quot;dronebotworkshop.com&quot; data-og-source-url=&quot;https://dronebotworkshop.com/esp32-intro/&quot; data-og-url=&quot;https://dronebotworkshop.com/esp32-intro/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/CjQoN/hyKfezKAO4/TjLGHlujHkufYUixqNB761/img.jpg?width=768&amp;amp;height=432&amp;amp;face=0_0_768_432,https://scrap.kakaocdn.net/dn/bfl5SD/hyKgIMKRx9/91EsSBXyDEcXIB4mHyJ8n1/img.jpg?width=768&amp;amp;height=432&amp;amp;face=0_0_768_432,https://scrap.kakaocdn.net/dn/cU6lIs/hyKgEp3LoA/Sq1r48wLU36nLSGLErZDBk/img.jpg?width=750&amp;amp;height=587&amp;amp;face=0_0_750_587&quot;&gt;&lt;a href=&quot;https://dronebotworkshop.com/esp32-intro/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://dronebotworkshop.com/esp32-intro/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/CjQoN/hyKfezKAO4/TjLGHlujHkufYUixqNB761/img.jpg?width=768&amp;amp;height=432&amp;amp;face=0_0_768_432,https://scrap.kakaocdn.net/dn/bfl5SD/hyKgIMKRx9/91EsSBXyDEcXIB4mHyJ8n1/img.jpg?width=768&amp;amp;height=432&amp;amp;face=0_0_768_432,https://scrap.kakaocdn.net/dn/cU6lIs/hyKgEp3LoA/Sq1r48wLU36nLSGLErZDBk/img.jpg?width=750&amp;amp;height=587&amp;amp;face=0_0_750_587');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Getting Started with the ESP32 - Using the Arduino IDE&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Learn how to use the Espressif ESP32 microcontroller with the Arduino IDE. Step-by-step instructions for getting started with the ESP32.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;dronebotworkshop.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://lastminuteengineers.com/esp32-arduino-ide-tutorial/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://lastminuteengineers.com/esp32-arduino-ide-tutorial/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1621336213879&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;https://lastminuteengineers.com/esp32-arduino-ide-tutorial/&quot; data-og-description=&quot;&quot; data-og-host=&quot;lastminuteengineers.com&quot; data-og-source-url=&quot;https://lastminuteengineers.com/esp32-arduino-ide-tutorial/&quot; data-og-url=&quot;https://lastminuteengineers.com/esp32-arduino-ide-tutorial/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://lastminuteengineers.com/esp32-arduino-ide-tutorial/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://lastminuteengineers.com/esp32-arduino-ide-tutorial/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;https://lastminuteengineers.com/esp32-arduino-ide-tutorial/&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;lastminuteengineers.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Development/ESP32</category>
      <author>독행소년</author>
      <guid isPermaLink="true">https://here4you.tistory.com/275</guid>
      <comments>https://here4you.tistory.com/275#entry275comment</comments>
      <pubDate>Tue, 18 May 2021 20:10:58 +0900</pubDate>
    </item>
    <item>
      <title>Flutter와 Arduino간 한글이 포함된 시리얼 통신 방법(base64, utf-8)</title>
      <link>https://here4you.tistory.com/274</link>
      <description>&lt;p&gt;시리얼 통신에서는 한글 데이터를 송수신 할 수 없다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;시리얼 통신에서는 문자당 1바이트를 할당하는 아스키방식이 사용되는데 한글은 문자당 2바이트를 사용하기 때문에 아스키코드 방식을 이용할 수 없기 때문이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;몇가지 해결방식이 있지만, Flutter 앱과 Arduino간에 한글 데이터의 교환이 필요한 경우에는 utf-8과 base64 인코딩/디코딩 방식을 이용하면 해결할 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Flutter에서 한글이 포함된 String을 인코딩/디코딩하는 방법은 다음과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1620882673771&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'dart:convert';

// 인코딩
String korean = base64.encode(utf8.encode(&quot;한글데이터&quot;));

// 디코딩
utf8.decode(base64.decode(korean));&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;인코딩한 문자열을 BLE나 WIFI 통신을 통해 아두이노로 전송하고, 반대로 수신한 문자열을 반대로 디코딩을 하면 복원할 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;다만 아두이노 자체가 기본적으로 아스키코드만을 지원하기 때문에 아두이노측에서 수신된 한글 데이터를 바로 이용하는 것은 불가능하다고 한다.&lt;/p&gt;
&lt;p&gt;임의의 라이브러리 주입을 통해 아두이노 IDE의 시리얼 모니터에 출력되는 데이터의 한글을 정상출력되도록 할 수는 있다.&lt;/p&gt;</description>
      <category>Development/Flutter</category>
      <author>독행소년</author>
      <guid isPermaLink="true">https://here4you.tistory.com/274</guid>
      <comments>https://here4you.tistory.com/274#entry274comment</comments>
      <pubDate>Thu, 13 May 2021 14:14:21 +0900</pubDate>
    </item>
    <item>
      <title>ESP32에 대한 정리(작업중)</title>
      <link>https://here4you.tistory.com/273</link>
      <description>&lt;p&gt;&lt;b&gt;1. 국내 인증 현황&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;ESP32 모듈의 인증관련 정보는 제조사(Espressif)에서 확인 가능하며 국내 KC 인증을 받은 모델도 있다.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.espressif.com/en/support/documents/certificates?keys=KC&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.espressif.com/en/support/documents/certificates?keys=KC&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1618331035539&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Certificates | Espressif Systems&quot; data-og-description=&quot;If you wish to make enquiries about other certifications, please contact us. Filter Clear Download selected Select All Certification Issue Date Download ESP32-WROOM-32E &amp;amp; ESP32-WROOM-32UE KCC Certification 2021.01.28 ESP32-WROVER-E &amp;amp; ESP32-WROVER-IE KCC Ce&quot; data-og-host=&quot;www.espressif.com&quot; data-og-source-url=&quot;https://www.espressif.com/en/support/documents/certificates?keys=KC&quot; data-og-url=&quot;https://www.espressif.com/en/support/documents/certificates?keys=KC&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/gx6b4/hyJSbv3ReW/PJt3glEdPEwgkJ5fkbhUK1/img.jpg?width=2851&amp;amp;height=571&amp;amp;face=0_0_2851_571&quot;&gt;&lt;a href=&quot;https://www.espressif.com/en/support/documents/certificates?keys=KC&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.espressif.com/en/support/documents/certificates?keys=KC&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/gx6b4/hyJSbv3ReW/PJt3glEdPEwgkJ5fkbhUK1/img.jpg?width=2851&amp;amp;height=571&amp;amp;face=0_0_2851_571');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;Certificates | Espressif Systems&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;If you wish to make enquiries about other certifications, please contact us. Filter Clear Download selected Select All Certification Issue Date Download ESP32-WROOM-32E &amp;amp; ESP32-WROOM-32UE KCC Certification 2021.01.28 ESP32-WROVER-E &amp;amp; ESP32-WROVER-IE KCC Ce&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;www.espressif.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;ESP32 기반 KC 인증 모델&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ESP32-WROOM-32E&lt;/li&gt;
&lt;li&gt;ESP32-WROOM-32UE&lt;/li&gt;
&lt;li&gt;ESP32-WROOM-32D&lt;/li&gt;
&lt;li&gt;ESP32-WROOM-32U&lt;/li&gt;
&lt;li&gt;ESP32-WROVER-E&lt;/li&gt;
&lt;li&gt;ESP32-WROVER-IE&lt;/li&gt;
&lt;li&gt;ESP32-WROVER-B&lt;/li&gt;
&lt;li&gt;ESP32-WROVER-IB&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;ESP8266 기반 KC 인증 모델&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ESP-WROOM-02D&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;국립전파연구원에서 esp32 모델로 검색을 하면 다음과 같은 인증 정보를 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/M9shG/btq2u3BE0AS/3zzpK3pbeMboXx2RJ4Mrq0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/M9shG/btq2u3BE0AS/3zzpK3pbeMboXx2RJ4Mrq0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/M9shG/btq2u3BE0AS/3zzpK3pbeMboXx2RJ4Mrq0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FM9shG%2Fbtq2u3BE0AS%2F3zzpK3pbeMboXx2RJ4Mrq0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;2. ESP32-WROOM-32 Vs, ESP32-WROOM 모듈 비교&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;자세한 내용은 제조사 페이지에서 확인 가능하다.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.espressif.com/projects/esp-idf/en/latest/esp32/hw-reference/modules-and-boards.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;docs.espressif.com/projects/esp-idf/en/latest/esp32/hw-reference/modules-and-boards.html&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;정리하면..&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;두 시리즈 모두 &lt;b&gt;ESP32-D0WD SoC&lt;/b&gt;를 기반으로한다.&lt;/p&gt;
&lt;p&gt;메모리 사양: ROM: 448KB, SRAM: 520KB&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;Wi-Fi와 BR/EDR + Bluetooth LE 4.2를 지원한다. (SoC 칩 내부에 통신 모듈이 내장되어 있다.)&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;ESP32-WROOM-32 시리즈 모듈은 기본 4MB의 Flash 메모리를 내장하고 있으며, 옵션에 따라 8, 16MB의 Flash 메모리를 가지는 파생모델도 있다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;ESP32-WROVER-E &lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;시리즈 모듈은 &lt;span style=&quot;color: #333333;&quot;&gt;ESP32-WROOM-32 시리즈 모듈의 사양에 추가로 8MB의 PSRAM을 가진다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;3. ESP32-WROOM-32 Vs, ESP32-&lt;span style=&quot;color: #333333;&quot;&gt;WROVER-E &lt;/span&gt;기반의 개발 보드&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;ESP32-DevKitC V4&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;a href=&quot;https://docs.espressif.com/projects/esp-idf/en/latest/esp32/hw-reference/esp32/get-started-devkitc.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;docs.espressif.com/projects/esp-idf/en/latest/esp32/hw-reference/esp32/get-started-devkitc.html&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1618335089785&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;website&quot; data-og-title=&quot;ESP32-DevKitC V4 Getting Started Guide - ESP32 -  &amp;mdash; ESP-IDF Programming Guide latest documentation&quot; data-og-description=&quot;Note The pins D0, D1, D2, D3, CMD and CLK are used internally for communication between ESP32 and SPI flash memory. They are grouped on both sides near the USB connector. Avoid using these pins, as it may disrupt access to the SPI flash memory / SPI RAM.&quot; data-og-host=&quot;docs.espressif.com&quot; data-og-source-url=&quot;https://docs.espressif.com/projects/esp-idf/en/latest/esp32/hw-reference/esp32/get-started-devkitc.html&quot; data-og-url=&quot;https://docs.espressif.com/projects/esp-idf/en/latest/esp32/hw-reference/esp32/get-started-devkitc.html&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/mADJg/hyJSmqQHKy/iHkA9pUERCfAkHsKAJhY0K/img.jpg?width=960&amp;amp;height=540&amp;amp;face=0_0_960_540,https://scrap.kakaocdn.net/dn/O3Gd6/hyJSqfJpVc/78Qq1qdsLkWKhKhr08tUhk/img.jpg?width=960&amp;amp;height=540&amp;amp;face=0_0_960_540,https://scrap.kakaocdn.net/dn/fZTlh/hyJSk0Szhh/MW06QxqaVpScG5VGeNNrv1/img.png?width=487&amp;amp;height=644&amp;amp;face=0_0_487_644&quot;&gt;&lt;a href=&quot;https://docs.espressif.com/projects/esp-idf/en/latest/esp32/hw-reference/esp32/get-started-devkitc.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.espressif.com/projects/esp-idf/en/latest/esp32/hw-reference/esp32/get-started-devkitc.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/mADJg/hyJSmqQHKy/iHkA9pUERCfAkHsKAJhY0K/img.jpg?width=960&amp;amp;height=540&amp;amp;face=0_0_960_540,https://scrap.kakaocdn.net/dn/O3Gd6/hyJSqfJpVc/78Qq1qdsLkWKhKhr08tUhk/img.jpg?width=960&amp;amp;height=540&amp;amp;face=0_0_960_540,https://scrap.kakaocdn.net/dn/fZTlh/hyJSk0Szhh/MW06QxqaVpScG5VGeNNrv1/img.png?width=487&amp;amp;height=644&amp;amp;face=0_0_487_644');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;ESP32-DevKitC V4 Getting Started Guide - ESP32 - &amp;mdash; ESP-IDF Programming Guide latest documentation&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;Note The pins D0, D1, D2, D3, CMD and CLK are used internally for communication between ESP32 and SPI flash memory. They are grouped on both sides near the USB connector. Avoid using these pins, as it may disrupt access to the SPI flash memory / SPI RAM.&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;docs.espressif.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&lt;a href=&quot;https://www.espressif.com/en/products/devkits/esp32-devkitc&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;www.espressif.com/en/products/devkits/esp32-devkitc&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1618335638102&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;website&quot; data-og-title=&quot;ESP32-DevKitC Board I Espressif&quot; data-og-description=&quot;Rapid Prototyping ESP32-DevKitC achieves optimal RF performance. You can get right into application design and development, without worrying about RF performance and antenna design. ESP32-DevKitC has your basic system-requirements already covered. Just plu&quot; data-og-host=&quot;www.espressif.com&quot; data-og-source-url=&quot;https://www.espressif.com/en/products/devkits/esp32-devkitc&quot; data-og-url=&quot;https://www.espressif.com/en/products/devkits/esp32-devkitc&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bLTAZh/hyJSmYGHMr/s83j7O11XVnPKkrXSTQKUK/img.jpg?width=558&amp;amp;height=210&amp;amp;face=0_0_558_210,https://scrap.kakaocdn.net/dn/cohkkY/hyJSlrXJVg/nwfKcHas596QbDyIwbQex0/img.png?width=282&amp;amp;height=282&amp;amp;face=0_0_282_282,https://scrap.kakaocdn.net/dn/buSFyl/hyJSjVdELx/hmCLsd49klEHm5szPlznI1/img.png?width=282&amp;amp;height=282&amp;amp;face=0_0_282_282&quot;&gt;&lt;a href=&quot;https://www.espressif.com/en/products/devkits/esp32-devkitc&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.espressif.com/en/products/devkits/esp32-devkitc&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bLTAZh/hyJSmYGHMr/s83j7O11XVnPKkrXSTQKUK/img.jpg?width=558&amp;amp;height=210&amp;amp;face=0_0_558_210,https://scrap.kakaocdn.net/dn/cohkkY/hyJSlrXJVg/nwfKcHas596QbDyIwbQex0/img.png?width=282&amp;amp;height=282&amp;amp;face=0_0_282_282,https://scrap.kakaocdn.net/dn/buSFyl/hyJSjVdELx/hmCLsd49klEHm5szPlznI1/img.png?width=282&amp;amp;height=282&amp;amp;face=0_0_282_282');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;ESP32-DevKitC Board I Espressif&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;Rapid Prototyping ESP32-DevKitC achieves optimal RF performance. You can get right into application design and development, without worrying about RF performance and antenna design. ESP32-DevKitC has your basic system-requirements already covered. Just plu&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;www.espressif.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;동일한 PCB 인터페이스를 가지면서 모듈과 안테나 형태만 조금씩 다른 장비가 판매되고 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Development/ETC</category>
      <author>독행소년</author>
      <guid isPermaLink="true">https://here4you.tistory.com/273</guid>
      <comments>https://here4you.tistory.com/273#entry273comment</comments>
      <pubDate>Wed, 14 Apr 2021 02:41:35 +0900</pubDate>
    </item>
    <item>
      <title>[MongoDB] Ubuntu 18.04에 MongoDB 설치 방법</title>
      <link>https://here4you.tistory.com/272</link>
      <description>&lt;p&gt;다음의 mongoDB 사이트 문서를 참고한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.mongodb.com/manual/tutorial/install-mongodb-on-ubuntu/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;docs.mongodb.com/manual/tutorial/install-mongodb-on-ubuntu/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1606288757675&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Install MongoDB Community Edition on Ubuntu &amp;mdash; MongoDB Manual&quot; data-og-description=&quot;&quot; data-og-host=&quot;docs.mongodb.com&quot; data-og-source-url=&quot;https://docs.mongodb.com/manual/tutorial/install-mongodb-on-ubuntu/&quot; data-og-url=&quot;https://docs.mongodb.com/manual/tutorial/install-mongodb-on-ubuntu/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/EWQAT/hyIl1oou8h/QALD95Kqa1C6LAlsHKvAd0/img.png?width=1024&amp;amp;height=512&amp;amp;face=0_0_1024_512&quot;&gt;&lt;a href=&quot;https://docs.mongodb.com/manual/tutorial/install-mongodb-on-ubuntu/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.mongodb.com/manual/tutorial/install-mongodb-on-ubuntu/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/EWQAT/hyIl1oou8h/QALD95Kqa1C6LAlsHKvAd0/img.png?width=1024&amp;amp;height=512&amp;amp;face=0_0_1024_512');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;Install MongoDB Community Edition on Ubuntu &amp;mdash; MongoDB Manual&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;docs.mongodb.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;1. MongoDB의 public GPG key를 주입한다.&lt;/p&gt;
&lt;pre id=&quot;code_1606288834573&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ubuntu@dev:~$ wget -qO - https://www.mongodb.org/static/pgp/server-4.4.asc | sudo apt-key add -
OK&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;2. MongoDB를 위한 리스트파일을 생성한다.&lt;/p&gt;
&lt;pre id=&quot;code_1606289063544&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ubuntu@dev:~$ echo &quot;deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu bionic/mongodb-org/4.4 multiverse&quot;
| sudo tee /etc/apt/sources.list.d/mongodb-org-4.4.list
deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu bionic/mongodb-org/4.4 multiverse&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;3. 로컬 패키지 데이터베이스를 갱신한다.&lt;/p&gt;
&lt;pre id=&quot;code_1606289106843&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ubuntu@dev:~$ sudo apt-get update
Hit:1 http://ap-northeast-2.ec2.archive.ubuntu.com/ubuntu bionic InRelease
Hit:2 http://ap-northeast-2.ec2.archive.ubuntu.com/ubuntu bionic-updates InRelease
Hit:3 http://ap-northeast-2.ec2.archive.ubuntu.com/ubuntu bionic-backports InRelease
Get:4 http://security.ubuntu.com/ubuntu bionic-security InRelease [88.7 kB]
Ign:5 https://repo.mongodb.org/apt/ubuntu bionic/mongodb-org/4.4 InRelease
Get:6 https://repo.mongodb.org/apt/ubuntu bionic/mongodb-org/4.4 Release [5391 B]
Get:7 https://repo.mongodb.org/apt/ubuntu bionic/mongodb-org/4.4 Release.gpg [801 B]
Get:8 https://repo.mongodb.org/apt/ubuntu bionic/mongodb-org/4.4/multiverse arm64 Packages [5112 B]
Get:9 https://repo.mongodb.org/apt/ubuntu bionic/mongodb-org/4.4/multiverse amd64 Packages [6838 B]
Fetched 107 kB in 2s (60.5 kB/s)
Reading package lists... Done
ubuntu@dev:~$&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;4. MongoDB를 설치한다.&lt;/p&gt;
&lt;pre id=&quot;code_1606289151516&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ubuntu@dev:~$ sudo apt-get install -y mongodb-org&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;5. MoongoDB 서비스를 실행한다.&lt;/p&gt;
&lt;pre id=&quot;code_1606289298426&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ubuntu@dev:~$ sudo service mongod start&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;6. MongoDB에 접속한다.&lt;/p&gt;
&lt;pre id=&quot;code_1606289328030&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ubuntu@dev:~$ mongo
MongoDB shell version v4.4.2
connecting to: mongodb://127.0.0.1:27017/?compressors=disabled&amp;amp;gssapiServiceName=mongodb
Implicit session: session { &quot;id&quot; : UUID(&quot;98b58ce7-e220-46e1-9d66-c32ebbd7c30c&quot;) }
MongoDB server version: 4.4.2
Welcome to the MongoDB shell.
For interactive help, type &quot;help&quot;.
For more comprehensive documentation, see
        https://docs.mongodb.com/
Questions? Try the MongoDB Developer Community Forums
        https://community.mongodb.com
---
The server generated these startup warnings when booting:
        2020-11-25T07:27:46.807+00:00: Using the XFS filesystem is strongly recommended with the WiredTiger storage engine. See http://dochub.mongodb.org/core/prodnotes-filesystem
        2020-11-25T07:27:47.571+00:00: Access control is not enabled for the database. Read and write access to data and configuration is unrestricted
---
---
        Enable MongoDB's free cloud-based monitoring service, which will then receive and display
        metrics about your deployment (disk utilization, CPU, operation statistics, etc).

        The monitoring data will be available on a MongoDB website with a unique URL accessible to you
        and anyone you share the URL with. MongoDB may use this information to make product
        improvements and to suggest MongoDB products and deployment options to you.

        To enable free monitoring, run the following command: db.enableFreeMonitoring()
        To permanently disable this reminder, run the following command: db.disableFreeMonitoring()
---
&amp;gt; exit
bye
ubuntu@dev:~$&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;exit 명령어로 빠져나올 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;7. 관리자 계정 추가&lt;/p&gt;
&lt;p&gt;MongoDB에 다시 접속하여 관리자 계정을 추가한다.&lt;/p&gt;
&lt;pre id=&quot;code_1606289580367&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ubuntu@dev:~$ mongo
MongoDB shell version v4.4.2
connecting to: mongodb://127.0.0.1:27017/?compressors=disabled&amp;amp;gssapiServiceName=mongodb
Implicit session: session { &quot;id&quot; : UUID(&quot;d8bf7079-140f-4e9a-a774-cb5926cc08a5&quot;) }
MongoDB server version: 4.4.2
---
The server generated these startup warnings when booting:
        2020-11-25T07:27:46.807+00:00: Using the XFS filesystem is strongly recommended with the WiredTiger storage engine. See http://dochub.mongodb.org/core/prodnotes-filesystem
        2020-11-25T07:27:47.571+00:00: Access control is not enabled for the database. Read and write access to data and configuration is unrestricted
---
---
        Enable MongoDB's free cloud-based monitoring service, which will then receive and display
        metrics about your deployment (disk utilization, CPU, operation statistics, etc).

        The monitoring data will be available on a MongoDB website with a unique URL accessible to you
        and anyone you share the URL with. MongoDB may use this information to make product
        improvements and to suggest MongoDB products and deployment options to you.

        To enable free monitoring, run the following command: db.enableFreeMonitoring()
        To permanently disable this reminder, run the following command: db.disableFreeMonitoring()
---
&amp;gt; use admin
switched to db admin
&amp;gt; db.createUser({user: 'test', pwd: 'test1234', roles: ['root']})
Successfully added user: { &quot;user&quot; : &quot;test&quot;, &quot;roles&quot; : [ &quot;root&quot; ] }
&amp;gt; exit
bye
ubuntu@dev:~$&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;8. 인증 설정&lt;/p&gt;
&lt;p&gt;MongoDB에 접속할 때 인증과정이 이루어지도록 설정파일을 수정한다.&lt;/p&gt;
&lt;p&gt;우선 MongoDB를 종료한 후 수정을 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1606289740634&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ubuntu@dev:~$ sudo service mongod stop
ubuntu@dev:~$ sudo vim /etc/mongod.conf&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;mongod.conf 설정파일의 security 항목의 주석을 해제한 후 그 하단에 authorization: enabled 를 추가한다.&lt;/p&gt;
&lt;pre id=&quot;code_1606289822792&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# mongod.conf

# for documentation of all options, see:
#   http://docs.mongodb.org/manual/reference/configuration-options/

# Where and how to store data.
storage:
  dbPath: /var/lib/mongodb
  journal:
    enabled: true
#  engine:
#  mmapv1:
#  wiredTiger:

# where to write logging data.
systemLog:
  destination: file
  logAppend: true
  path: /var/log/mongodb/mongod.log

# network interfaces
net:
  port: 27017
  bindIp: 127.0.0.1


# how the process runs
processManagement:
  timeZoneInfo: /usr/share/zoneinfo

security:
  authorization: enabled

#operationProfiling:

#replication:

#sharding:

## Enterprise-Only Options:

#auditLog:

#snmp:
~&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;9. 인증 접속&lt;/p&gt;
&lt;p&gt;MongoDB를 다시 실행한 후 등록한 계정 정보로 접속을 시도해 보자.&lt;/p&gt;
&lt;pre id=&quot;code_1606289940728&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ubuntu@dev:~$ sudo service mongod start
ubuntu@dev:~$ mongo admin -u test -p test1234
MongoDB shell version v4.4.2
connecting to: mongodb://127.0.0.1:27017/admin?compressors=disabled&amp;amp;gssapiServiceName=mongodb
Implicit session: session { &quot;id&quot; : UUID(&quot;5e6ae32d-901a-42b6-b1c6-2391482b2cec&quot;) }
MongoDB server version: 4.4.2
---
The server generated these startup warnings when booting:
        2020-11-25T07:38:12.870+00:00: Using the XFS filesystem is strongly recommended with the WiredTiger storage engine. See http://dochub.mongodb.org/core/prodnotes-filesystem
---
---
        Enable MongoDB's free cloud-based monitoring service, which will then receive and display
        metrics about your deployment (disk utilization, CPU, operation statistics, etc).

        The monitoring data will be available on a MongoDB website with a unique URL accessible to you
        and anyone you share the URL with. MongoDB may use this information to make product
        improvements and to suggest MongoDB products and deployment options to you.

        To enable free monitoring, run the following command: db.enableFreeMonitoring()
        To permanently disable this reminder, run the following command: db.disableFreeMonitoring()
---
&amp;gt; exit
bye
ubuntu@dev:~$&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;10. 외부 접속 설정&lt;/p&gt;
&lt;p&gt;MongoDB는 기본적으로 로컬호스트에서만 접속이 가능하도록 설정되어 있다. 외부 접속을 위해서는 net 항목의 bindIp 항목을 0.0.0.0으로 변경한다.&lt;/p&gt;
&lt;pre id=&quot;code_1606290167612&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# mongod.conf

# for documentation of all options, see:
#   http://docs.mongodb.org/manual/reference/configuration-options/

# Where and how to store data.
storage:
  dbPath: /var/lib/mongodb
  journal:
    enabled: true
#  engine:
#  mmapv1:
#  wiredTiger:

# where to write logging data.
systemLog:
  destination: file
  logAppend: true
  path: /var/log/mongodb/mongod.log

# network interfaces
net:
  port: 27017
  bindIp: 0.0.0.0


# how the process runs
processManagement:
  timeZoneInfo: /usr/share/zoneinfo

security:
  authorization: enabled

#operationProfiling:

#replication:

#sharding:

## Enterprise-Only Options:

#auditLog:

#snmp:
~
~&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;추가로 사용할 Port를 변경하려면 port 항목도 변경한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;11. 외부 접속 시도&lt;/p&gt;
&lt;p&gt;외부에서 서버에 설치된 MongoDB로 접속하기 위해 GUI 툴을 이용해보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;다음 링크에서 Robo3T를 다운로드 한다.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://robomongo.org/download&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;robomongo.org/download&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1606290579963&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Robomongo&quot; data-og-description=&quot;Robo 3T: Simple GUI for beginners Robo 3T 1.4 brings support for MongoDB 4.2, a mongo shell upgrade from 4.0 to 4.2, the ability to manually specify visible databases, and many other fixes and improvements. View the full blog post. &amp;nbsp;&amp;nbsp;Download Robo 3T&amp;nbsp;&amp;nbsp;&quot; data-og-host=&quot;robomongo.org&quot; data-og-source-url=&quot;https://robomongo.org/download&quot; data-og-url=&quot;https://robomongo.org/download&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://robomongo.org/download&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://robomongo.org/download&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;Robomongo&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;Robo 3T: Simple GUI for beginners Robo 3T 1.4 brings support for MongoDB 4.2, a mongo shell upgrade from 4.0 to 4.2, the ability to manually specify visible databases, and many other fixes and improvements. View the full blog post. &amp;nbsp;&amp;nbsp;Download Robo 3T&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;robomongo.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;Robo3T를 설치한 후 실행한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;연결 설정에서 서버의 주소와 포트번호를 입력한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;이미지 1.png&quot; data-origin-width=&quot;502&quot; data-origin-height=&quot;458&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yHeeS/btqOhODGVh6/FGH2cKwP5ndVKUu9cdLLQk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yHeeS/btqOhODGVh6/FGH2cKwP5ndVKUu9cdLLQk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yHeeS/btqOhODGVh6/FGH2cKwP5ndVKUu9cdLLQk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyHeeS%2FbtqOhODGVh6%2FFGH2cKwP5ndVKUu9cdLLQk%2Fimg.png&quot; data-filename=&quot;이미지 1.png&quot; data-origin-width=&quot;502&quot; data-origin-height=&quot;458&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;인증 설정에서 생성했던 계정 정보를 입력한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;이미지 3.png&quot; data-origin-width=&quot;502&quot; data-origin-height=&quot;458&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwZS4h/btqOguFI4v2/Hzv4oh5jm7sFu1aMShVDtk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwZS4h/btqOguFI4v2/Hzv4oh5jm7sFu1aMShVDtk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwZS4h/btqOguFI4v2/Hzv4oh5jm7sFu1aMShVDtk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbwZS4h%2FbtqOguFI4v2%2FHzv4oh5jm7sFu1aMShVDtk%2Fimg.png&quot; data-filename=&quot;이미지 3.png&quot; data-origin-width=&quot;502&quot; data-origin-height=&quot;458&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;설정을 저장하고 연결을 시도한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;연결에 성공하면 다음과 같이 MongoDB의 내용을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;이미지 4.png&quot; data-origin-width=&quot;782&quot; data-origin-height=&quot;598&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QZCIi/btqOda2iGxc/Sy7Wij4NyI8K7PCuKamd1K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QZCIi/btqOda2iGxc/Sy7Wij4NyI8K7PCuKamd1K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QZCIi/btqOda2iGxc/Sy7Wij4NyI8K7PCuKamd1K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQZCIi%2FbtqOda2iGxc%2FSy7Wij4NyI8K7PCuKamd1K%2Fimg.png&quot; data-filename=&quot;이미지 4.png&quot; data-origin-width=&quot;782&quot; data-origin-height=&quot;598&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Development/Cloud computing</category>
      <author>독행소년</author>
      <guid isPermaLink="true">https://here4you.tistory.com/272</guid>
      <comments>https://here4you.tistory.com/272#entry272comment</comments>
      <pubDate>Wed, 25 Nov 2020 17:10:22 +0900</pubDate>
    </item>
    <item>
      <title>인생은 참 짧다.</title>
      <link>https://here4you.tistory.com/271</link>
      <description>&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;인생은 참 짧다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;사랑하기만 하면서 살기도 짧다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그런데 시기하고 미워하고 싸우면서 시간을 허비한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;가장 큰 문제는...&lt;/p&gt;
&lt;p&gt;이래서.. 또 저래서.. 난 이미 끝났어... 늦었어...&lt;/p&gt;
&lt;p&gt;그래서 그런건 해봐야 소용이 없어......&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;시작도 전에 포기하는 거 아닐까?&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그래 안될 수도 있지.. 쉽지 않을 수도 있어...&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;근데 그럼 내 나이 마흔에 다 포기할까? 체념할까?&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;안되면 말지...&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;근데 말이야...&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그래도 한번 해볼래....&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;눈감을 때 후회하지 않게....&lt;/p&gt;
&lt;p&gt;못해서도 후회하겠지만...&lt;/p&gt;
&lt;p&gt;시도도 안하면 더 후회할거 같아...&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Log</category>
      <author>독행소년</author>
      <guid isPermaLink="true">https://here4you.tistory.com/271</guid>
      <comments>https://here4you.tistory.com/271#entry271comment</comments>
      <pubDate>Sun, 12 Jul 2020 00:58:22 +0900</pubDate>
    </item>
    <item>
      <title>Docker란? | Docker를 사용하는 이유 | Docker 장점</title>
      <link>https://here4you.tistory.com/270</link>
      <description>&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;최근 본 블로그에 Docker 사용법을 몇 번 포스팅 했다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;Docker를 사용하기 전 Docker가 무엇이며 왜 사용해야 하는지에 대해 짧게 정리해보려고 한다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;이미 Docker에 대해 자세하게 잘 설명된 블로그들이 많다. 그리고 좋다는 소문이 많으니 나도 Docker를 한번 사용해 볼까? 그런데 Docker가 뭐고 왜 사용해야 하지? 하는 사람들에게 조언하자면..&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;Docker란?&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;Docker는 서버 가상화 기술 중 한 종류다. 물론 일반 PC에서도 사용 가능하다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;가상화란?&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;그럼 가상화란 무엇인가?&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;기존에 VMware나 VirtualBox 등을 사용해본 사용자라면 쉽게 이해하겠지만 이미 운영체제를 설치한 상황에서 VM(Virtual Machine)을 설치하고 그 위해 또 다른 운영체제를 설치하는 기술이다. 윈도우를 설치한 PC에서 우분투 등의 리눅스 머신이 필요할 때 VMware를 설치하고 그 위해 우분투를 설치하면 하나의 PC에서 윈도우와 우분투를 동시에 사용할 수 있게 되는 방식이다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;Docker도 가상화 기술이므로 동일하게 호스트 OS 위에 복수개의 게스트 OS를 복수개 설치할 수 있게 해준다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;왜 가상화 기술을 사용하지?&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;이론적인 가상화 기술을 목적은 컴퓨팅 자원의 활용률을 높이기 위해서다. 하나의 컴퓨터에 복수의 OS를 동시에 동작시키면 논리적으로는 복수개의 컴퓨팅 환경이 준비되기 때문에 자원 활용률이 높아진다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;그럼 우린 왜 사용하는가? 본인의 경우는 개발자이기 때문에 개발환경이 필요해서 사용해 왔다. 호스트 OS는 윈도우를 사용하고, 추가로 개발용 OS로 우분투를 주로 사용하는데, 가상화 기술을 이용하면 한 대에 PC에서 윈도우와 우분투를 동시에 사용할 수 있기 때문이다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;또 본인은 서버도 개발하는데 개발용 서버와 운영용 서버가 별도로 필요하다. 더 나아가 개발용 서버는 빈번하게 갈아엎어야 할 때도 많다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;근래에는 거의 클라우드 컴퓨팅 서비스를 이용하지만 인스턴스를 생성할 때마다 개발환경을 설치와 설정을하는 반복 것도 번거롭다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;이럴 때 잘 세팅된 환경을 이미지로 구워 놓았다가 개발환경을 갈아 엎을 때 마다 미리 준비했던 이미지를 이용하면 번거로운 작업을 생략할 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;그럼 왜 Docker인가?&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;성능 측면&lt;/h4&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;VMware를 사용해 본 사람이라면 자주 느꼈을 것이다. VMware는 많이 무겁다. 그러므로 그 위해서 동작하는 게스트 OS도 꽤 무겁게 동작한다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;왜 무거웠을까?&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;VMware와 같은 기존의 가상화 방식에서는 하이퍼바이저(Hypervisor)라는 VM이 존재하고 그 위에 다시 호스트 OS가 설치된 후 최종적으로 호스트 응용이 실행되게 된다. 응용 프로그램에 입장에서는 게스트 OS와 하이퍼바이저를 거쳐야 호스트 OS를 통해 컴퓨텅 자원에 접근할 수 있게 된다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;Docker의 경우는 어떨까?&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;간단히 설명하면, Docker의 경우는 별도의 게스트 OS는 설치되지도 않고, 별도의 하이퍼바이저도 거치지 않으며, Docker 엔진을 통해 바로 호스트 OS를 통해 컴퓨팅 자원에 접근할 수 있다. 컨테이터 응용의 성능 저하는 1% 정도라고 하니 &lt;span style=&quot;color: #333333;&quot;&gt;호스트 OS에서 실행되는 응용이나 Docker 컨테이터에서 실행되는 응용이나 거의 동일한 퍼포먼스를 제공할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;배포 측면&lt;/span&gt;&lt;/h4&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;개발환경을 이미지로 만들어서 배포할 때 차이점은 어떨까?&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;VMware의 경우 이미지 안에는 게스트 OS와 사용자가 설치한 각종 라이브러리와 프로그램, 그리고 개발 소스코드 등이 존재하며 실제로 Gbyte 단위의 이미지가 생성되게 된다. 이 크기의 이미지를 배포하는 것 자체가 부담이다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;Docker의 경우는, 성능 부분에서 잠깐 언급했지만 별도의 게스트 OS의 설치가 필요 없다. 그러므로 이미지 안에는 사용자가 직접 추가한 라이브러리나 프로그램, 소스코드 등만 포함되므로 수십 Mbyte나 수백 Mbyte 정도 크기의 이미지로 생성된다. 이정도 크기면 쉽게 배포할 수 있는 크기라고 볼 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;실제로 Docker Hub를 이용해서 이미지를 쉽게 배포할 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;조금 더 추가 설명하면,&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;만약 개발 프로젝트에서 일(day) 단위로 개발환경을 이미지로 백업해서 형상관리한다고 가정해 보자.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;VMware를 이용하면 매일 Gbyte 단위의 이미지가 생성될 텐데, 1Gbyte라고만 가정해도 한달에 30GByte의 저장소가 추가로 필요하게 되고, 향후 이 이미지들을 다른 저장소로 옮긴다고.... 답답함이 몰려온다. 그런데 그 수백 수천 Gbyte의 이미지 중 실제로 개발내용은 몇 프로나 될 까? 1%도 안될 것이다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;Docker의 경우는 굉장히 효율적이다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;매번 새롭게 이미지를 생성하는 방식이 아니라, Git에서 소스코드를 관리하듯 현재의 환경을 이미지로 커밋(commit)하는 방식으로 이미지를 관리한다. 이미지는 커밋 될 때마다 변경된 내용만 기존 이미지에 추가된다. 수백 수천번 커밋하더라도 이미지는 수십 Mbyte나 늘어날까? VMware와 비교하면 비교가 안될만큼 효율적으로 이미지 관리 및 배포가 가능하다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;이상 Docker에 대해 개발자의 관점에서 간략하게 정리해 봤다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Development/Cloud computing</category>
      <author>독행소년</author>
      <guid isPermaLink="true">https://here4you.tistory.com/270</guid>
      <comments>https://here4you.tistory.com/270#entry270comment</comments>
      <pubDate>Thu, 9 Jul 2020 13:08:14 +0900</pubDate>
    </item>
    <item>
      <title>Docker 사용법 | 컨테이너와 이미지 상태 확인 및 삭제 방법</title>
      <link>https://here4you.tistory.com/269</link>
      <description>&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;b&gt;1. 컨테이너 상태 확인&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;실행 중인 컨테이너의 상태를 확인할 때에는 ps 명령어를 사용한다.&lt;/p&gt;
&lt;pre id=&quot;code_1593670787689&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ubuntu@here4you:~$ sudo docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS                  NAMES
a1b40c67d52d        study               &quot;/bin/bash&quot;         24 hours ago        Up 24 hours         0.0.0.0:6900-&amp;gt;80/tcp   study2
ubuntu@here4you:~$&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;현재 study2라는 컨테이너가 24시간 이상 실행 중이며, 호스트의 6900으로 수신된 요청을 80번 포트로 수신하고 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;그리고 study2 컨테이너는 study 이미지를 기반으로 생성된 것임을 알 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;호스트에 존재하는 모든 컨테이너를 확인할 때에는 ps 명령어와 함께 -a 옵션을 사용한다.&lt;/p&gt;
&lt;pre id=&quot;code_1593670894109&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ubuntu@here4you:~$ sudo docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                    PORTS                  NAMES
a1b40c67d52d        study               &quot;/bin/bash&quot;         24 hours ago        Up 24 hours               0.0.0.0:6900-&amp;gt;80/tcp   study2
c628d61e15e8        ubuntu:18.04        &quot;/bin/bash&quot;         24 hours ago        Exited (0) 24 hours ago                          myserver
a6c38cd53d5f        ubuntu:18.04        &quot;/bin/bash&quot;         25 hours ago        Exited (0) 24 hours ago                          study
ubuntu@here4you:~$&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;myserver와 study 컨테이너가 24시간 전에 종료된 상태임을 알 수 있다. IMAGE 항목을 보면 둘 다 ubuntu18.04 이미지를 이용해서 생성된 것임을 알 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;b&gt;2. 컨테이너 삭제&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;컨테이너를 삭제할 때에는 rm 명령어를 사용한다. study 컨테이너를 삭제한 후 컨테이너 정보를 다시 조회해 보자.&lt;/p&gt;
&lt;pre id=&quot;code_1593671019898&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ubuntu@here4you:~$ sudo docker rm study
study
ubuntu@here4you:~$ sudo docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                    PORTS                  NAMES
a1b40c67d52d        study               &quot;/bin/bash&quot;         24 hours ago        Up 24 hours               0.0.0.0:6900-&amp;gt;80/tcp   study2
c628d61e15e8        ubuntu:18.04        &quot;/bin/bash&quot;         24 hours ago        Exited (0) 24 hours ago                          myserver
ubuntu@here4you:~$&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;study 컨테이너가 삭제된 것을 확인할 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;이번에는 실행 중인 study2 컨테이너를 삭제 시도해 보자.&lt;/p&gt;
&lt;pre id=&quot;code_1593671101503&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ubuntu@here4you:~$ sudo docker rm study2
Error response from daemon: You cannot remove a running container a1b40c67d52d8cb246b6789bfd1e7931b1ff41c091c10f32e1f36173a898c95d. Stop the container before attempting removal or force remove
ubuntu@here4you:~$&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;실행 중인 컨테이너는 삭제할 수 없다는 문구가 출력된다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;b&gt;3. 이미지 상태 확인&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;로컬에 저장된 이미지의 정보를 확인할 때에는 images 명령어를 이용한다.&lt;/p&gt;
&lt;pre id=&quot;code_1593671209948&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ubuntu@here4you:~$ sudo docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
study               latest              6db604829661        24 hours ago        94.2MB
ubuntu              18.04               8e4ce0a6ce69        2 weeks ago         64.2MB
ubuntu@here4you:~$&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;현재 ubuntu 이미지와 지난 포스팅에서 생성한 study 이미지가 존재하는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;b&gt;4. 이미지 삭제&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;이미지를 삭제할 때에는 rmi 명령어를 사용한다. ubuntu 이미지를 삭제해 보자. 이미지 이름이 아닌 이미지의 ID를 이용해서 삭제 가능하다.&lt;/p&gt;
&lt;pre id=&quot;code_1593671321696&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ubuntu@here4you:~$ sudo docker rmi ubuntu:18.04
Error response from daemon: conflict: unable to remove repository reference &quot;ubuntu:18.04&quot; (must force) - container c628d61e15e8 is using its referenced image 8e4ce0a6ce69
ubuntu@here4you:~$&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;myserver(c628d61e15e8) 컨테이너가 ubuntu 이미지를 참조하고 있기 때문에 삭제가 불가능하다는 문구가 출력된다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;실제로 지난 포스팅에서 myserver 컨테이너를 ubuntu 이미지를 이용해서 생성했다. 그런데 왜 myserver 컨테이너 때문에 ubuntu 이미지를 삭제할 수 없을까?&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;이는 myserver 컨테이너를 커밋할 때 ubuntu 이미지를 참조하기 때문이다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;Docker는 컨테이너를 이미지로 커밋할 때 해당 컨테이너의 전체를 별도의 이미지로 생성하지 않는다. 해당 컨테이너를 생성할 때 사용한 이미지를 그대로 참조하면서 변경사항만을 추가해서 이미지를 생성한다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;현재 로컬에 저장된 study 이미지는 study 컨테이너를 커밋해서 생성했는데, 이 study 이미지는 ubuntu 이미지를 이용해서 생성한 것이다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;ubuntu 이미지 --생성--&amp;gt; study 컨테이너 --커밋--&amp;gt; study 이미지&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;다시 돌아와서 myserver 컨테이너가 커밋을 통해 이미지를 생성할 경우 ubuntu 이미지를 참조해야 하기 때문에 ubuntu 이미지를 삭제할 수 없는 것이다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;일반적으로 이미지를 삭제하려면, 해당 이미지를 참조하는 컨테이너를 먼저 삭제한 후 이미지를 삭제하는 것이 옳은 방법이다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;-f 옵션을 이용하면 이미지를 강제로 종료할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1593673081558&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ubuntu@here4you:~$ sudo docker rmi -f ubuntu:18.04
Untagged: ubuntu:18.04
Untagged: ubuntu@sha256:86510528ab9cd7b64209cbbe6946e094a6d10c6db21def64a93ebdd20011de1d
ubuntu@here4you:~$ sudo docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
study               latest              6db604829661        25 hours ago        94.2MB
ubuntu@here4you:~$&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Development/Cloud computing</category>
      <author>독행소년</author>
      <guid isPermaLink="true">https://here4you.tistory.com/269</guid>
      <comments>https://here4you.tistory.com/269#entry269comment</comments>
      <pubDate>Thu, 2 Jul 2020 16:00:15 +0900</pubDate>
    </item>
    <item>
      <title>Docker 사용법 | 컨테이너에 포트 부여 방법 | 이미지 생성 방법</title>
      <link>https://here4you.tistory.com/268</link>
      <description>&lt;p style=&quot;text-align: justify;&quot;&gt;Docker의 컨테이너를 생성할 때 기본적으로 모든 포트는 닫힌 상태로 생성되게 된다. 만약 컨테이너에서 서버를 운영할 경우 외부에서 컨터이너 내부의 서버로 접근하기 위해서는 해당 포트를 직접 부여해야 한다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;b&gt;1. 컨테이너 생성 시 포트를 부여하는 방법&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;컨테이너 생성시 포트를 부여하기 위해서는, -p 옵션을 이용해서 바인딩할 포트를 부여한다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;다음은 우부투 18.04 이미지를 이용해서 myserver라는 컨테이너를 생성하면서, 호스트 머신의 80번 포트를 컨테이너의 80번 포트로 바인딩 한 것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1593582880355&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ubuntu@here4you:~$ sudo docker run -it -p 80:80 --name myserver ubuntu:18.04
root@c628d61e15e8:/# ls
bin  boot  dev  etc  home  lib  lib64  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var
root@c628d61e15e8:/#&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;* 참고로 이번 컨테이너의 생성(run) 단계에는 지난 포스팅에서 컨테이너를 생성했을 때와 달리 별도의 다운로드 과정이 없이 신속하게 컨테이너가 생성되었다. 이는 한번 다운로드한 이미지(ubuntu 18.04)를 로컬에 저장하고 있다가 재사용했기 때문이다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;정상적인 포트 부여 여부를 확인하기 위해 nginx를 설치하고 실행해 보자.&lt;/p&gt;
&lt;pre id=&quot;code_1593583318266&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;root@c628d61e15e8:/# apt-get update
root@c628d61e15e8:/# apt-get upgrade
root@c628d61e15e8:/# apt-get install nginx
root@c628d61e15e8:/# service nginx start&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;웹브라우저를 열어 호스트 머신의 IP를 입력한 후 nginx에 접속이 가능한지 확인한다. 다음과 같이 출력되면 성공한 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;이미지 1.png&quot; data-origin-width=&quot;762&quot; data-origin-height=&quot;608&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cDoyUt/btqFftJ97YO/pLK1tktVRfJCca90ZqtJUk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cDoyUt/btqFftJ97YO/pLK1tktVRfJCca90ZqtJUk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cDoyUt/btqFftJ97YO/pLK1tktVRfJCca90ZqtJUk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcDoyUt%2FbtqFftJ97YO%2FpLK1tktVRfJCca90ZqtJUk%2Fimg.png&quot; data-filename=&quot;이미지 1.png&quot; data-origin-width=&quot;762&quot; data-origin-height=&quot;608&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;호스트 머신의 80번 포트로 수신된 요청이 컨테이너의 80번 포트를 통해 nginx까지 전달되고 그 응답이 돌아온 것이다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;b&gt;2. 이미 생성한 컨테이너에 포트를 부여하는 방법&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;지난 포스팅에서 별도의 포트를 부여하지 않은 study 컨테이너를 생성했다. 하지만 study 컨테이너에 직접 포트를 부여하는 방법은 없다. 이미 생성단계에서 포트를 부여하지 않았기 때문이다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;다만, 기존의 컨테이너를 이미지화하고 그 이미지를 이용해서 새로운 컨테이너를 생성하면서 포트를 부여하는 간접적인 방법으로 문제를 해결할 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;우선 지난 포스팅에서 생성한 study 컨테이너를 종료한다. (실행 중인 경우)&lt;/p&gt;
&lt;pre id=&quot;code_1593584451933&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ubuntu@here4you:~$ sudo docker stop study
study
ubuntu@here4you:~$&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;study 컨테이너를 커밋하여 study 이미지로 만든다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1593584555754&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ubuntu@here4you:~$ sudo docker commit study study
sha256:6db6048296618a19f42507becc28ce4b12d66a55fa43324e731416ee591cc67d
ubuntu@here4you:~$&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;우선 호스트에 존재하는 이미지들을 확인해 보자.&lt;/p&gt;
&lt;pre id=&quot;code_1593584611961&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ubuntu@here4you:~$ sudo docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
study               latest              6db604829661        44 seconds ago      94.2MB
ubuntu              18.04               8e4ce0a6ce69        2 weeks ago         64.2MB
ubuntu@here4you:~$&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;2주전에 생성된 ubuntu(18.04)의 이미지가 존재하며, 조금 전(44초 전) 생성한 study 이미지도 존재한다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;이제 study 이미지를 이용해서 새로운 컨테이너(study2)를 생성하면서 포트를 부여해 보자.&lt;/p&gt;
&lt;pre id=&quot;code_1593584920424&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ubuntu@here4you:~$ sudo docker run -it --name study2 -p 6900:80 study
root@a1b40c67d52d:/#&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;이번에는 호스트 머신의 6900 포트로 입려되는 요청을 study2 컨테이너의 80번 포트로 전달하도록 바인딩 했다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;앞서 기술한 방식과 동일하게 nginx를 설치하고 실행해 보자. 이번에는 호스트 IP 주소와 함께 포트번호 6900도 추가로 입력해야 한다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;정상적으로 nginx에 접근이 가능한 것을 확인하였다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1059&quot; data-origin-height=&quot;804&quot; data-filename=&quot;이미지 2.png&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Jdq1b/btqFiAOqvZt/4GmJh4aRZX4e4ljGHxQaX1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Jdq1b/btqFiAOqvZt/4GmJh4aRZX4e4ljGHxQaX1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Jdq1b/btqFiAOqvZt/4GmJh4aRZX4e4ljGHxQaX1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJdq1b%2FbtqFiAOqvZt%2F4GmJh4aRZX4e4ljGHxQaX1%2Fimg.png&quot; data-origin-width=&quot;1059&quot; data-origin-height=&quot;804&quot; data-filename=&quot;이미지 2.png&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;* 참고로 lightsail을 이용할 경우 호스트 인스턴스에 6900 포트를 열어주어야 한다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Development/Cloud computing</category>
      <author>독행소년</author>
      <guid isPermaLink="true">https://here4you.tistory.com/268</guid>
      <comments>https://here4you.tistory.com/268#entry268comment</comments>
      <pubDate>Wed, 1 Jul 2020 15:34:23 +0900</pubDate>
    </item>
    <item>
      <title>Docker 사용법 | 컨테이너 생성/종료/실행/진입</title>
      <link>https://here4you.tistory.com/267</link>
      <description>&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;b&gt;1. 컨테이너 생성&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;우분투 18.04 이미지를 이용해서 study라는 이름의 컨테이너를 생성한다.&lt;/p&gt;
&lt;pre id=&quot;code_1593581008601&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ubuntu@here4you:~$ sudo docker run -it --name study ubuntu:18.04
Unable to find image 'ubuntu:18.04' locally
18.04: Pulling from library/ubuntu
d7c3167c320d: Pull complete
131f805ec7fd: Pull complete
322ed380e680: Pull complete
6ac240b13098: Pull complete
Digest: sha256:86510528ab9cd7b64209cbbe6946e094a6d10c6db21def64a93ebdd20011de1d
Status: Downloaded newer image for ubuntu:18.04
root@a6c38cd53d5f:/#&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;-it는 인터랙션을 위한 옵션으로 -it 옵션을 추가해야 해당 컨테이너의 터미널로 접근 및 제어가 가능하다.&lt;/p&gt;
&lt;p&gt;a6c38cd53d5f는 컨테이너의 ID에 해당하며, 컨테이너에 root로 진입했음을 의미한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;2. 컨테이너 종료&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;진입했던 컨테이너를 종료하고 빠져나오려면 exit 명령어를 이용한다.&lt;/p&gt;
&lt;pre id=&quot;code_1593581664102&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;root@a6c38cd53d5f:/# ls
bin  boot  dev  etc  home  lib  lib64  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var
root@a6c38cd53d5f:/# exit
exit
ubuntu@here4you:~$&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 때 주의할 점은 exit 명령어로 컨네이터에서 빠져나오면 동시에 컨테이너도 종료된다는 것이다. 서버 등을 운영 중일 경우 exit를 이용하면 서비스가 종료될 수 있으니 주의해야한다.&lt;/p&gt;
&lt;p&gt;컨테이너 종료 없이 빠져나오려면 &lt;u&gt;&lt;b&gt;Ctrl + P, Q&lt;/b&gt;&lt;/u&gt;를 입력해야 한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;3. 컨테이너 시작 및 재진입&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;종료된 컨테이너를 시작할 때에는 start 명령어를 사용하고, 실행중인 컨테이너에 진입할 때에는 attach 명령어를 사용한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;우선 attach 명령어를 이용해서 생성했던 study 컨테이너로 진입을 시도해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1593581906943&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ubuntu@here4you:~$ sudo docker attach study
You cannot attach to a stopped container, start it first
ubuntu@here4you:~$&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;종료된 컨테이너로는 진입이 불가하니 실행을 먼저하라는 문구가 출력된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;컨테이너를 실행한 후 다시 진입해 보자.&lt;/p&gt;
&lt;pre id=&quot;code_1593582019867&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ubuntu@here4you:~$ sudo docker start study
study
ubuntu@here4you:~$ sudo docker attach study
root@a6c38cd53d5f:/# ls
bin  boot  dev  etc  home  lib  lib64  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var
root@a6c38cd53d5f:/#&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;정상적으로 진입되었다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;4. 컨테이너에서 빠져나온 후 재진입&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;Ctrl + P, Q를 입력하여 컨테이너에서 빠져나온 후 다시 attach 명령어로 재진입해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1593582093010&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;root@a6c38cd53d5f:/# ls
bin  boot  dev  etc  home  lib  lib64  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var
root@a6c38cd53d5f:/# read escape sequence
ubuntu@here4you:~$ sudo docker attach study
root@a6c38cd53d5f:/#&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;start 명령어 없이 바로 진입되는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;5. 컨테이너의 정보를 확인&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;ps 명령어를 이용하면 실행중인 컨테이너들의 정보를 확인할 수 있으며, -a 옵션을 추가하면 모든 컨테이너의 정보를 확인할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1593585437856&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ubuntu@here4you:~$ sudo docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS                  NAMES
a1b40c67d52d        study               &quot;/bin/bash&quot;         8 minutes ago       Up 8 minutes        0.0.0.0:6900-&amp;gt;80/tcp   study2
ubuntu@here4you:~$ sudo docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                      PORTS                  NAMES
a1b40c67d52d        study               &quot;/bin/bash&quot;         8 minutes ago       Up 8 minutes                0.0.0.0:6900-&amp;gt;80/tcp   study2
c628d61e15e8        ubuntu:18.04        &quot;/bin/bash&quot;         42 minutes ago      Exited (0) 23 minutes ago                          myserver
a6c38cd53d5f        ubuntu:18.04        &quot;/bin/bash&quot;         About an hour ago   Exited (0) 16 minutes ago                          study
ubuntu@here4you:~$&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Development/Cloud computing</category>
      <author>독행소년</author>
      <guid isPermaLink="true">https://here4you.tistory.com/267</guid>
      <comments>https://here4you.tistory.com/267#entry267comment</comments>
      <pubDate>Wed, 1 Jul 2020 14:43:31 +0900</pubDate>
    </item>
    <item>
      <title>Flutter 강좌 - File Picker나 Document Picker 플러그인이 동작하지 않을 때 해결 방법 | File Picker or Document Picker is not working</title>
      <link>https://here4you.tistory.com/266</link>
      <description>&lt;p style=&quot;font-size: 1.12em; text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em; text-align: justify;&quot;&gt;&lt;b&gt;Flutter Code Examples 강좌를 추천합니다.&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;제 블로그에서 Flutter Code Examples 프로젝트를 시작합니다.&lt;/li&gt;
&lt;li&gt;Flutter의 다양한 예제를 소스코드와 실행화면으로 제공합니다.&lt;/li&gt;
&lt;li&gt;또한 모든 예제는 &lt;a href=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Flutter Code Examples&lt;/a&gt; 앱을 통해 테스트 가능합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;a href=&quot;https://here4you.tistory.com/category/Tutorial/Flutter%20with%20App&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Flutter Code Examples 강좌로 메뉴로 이동&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;a href=&quot;https://here4you.tistory.com/213&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Flutter Code Examples 강좌 목록 페이지로 이동&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;a href=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Flutter Code Examples 앱 설치 | Google Play Store로 이동&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1571794790206&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Flutter Code Examples - Google Play 앱&quot; data-og-description=&quot;Are you a beginner at Flutter? Check out the various features of Flutter through the demo. Source code for all demos is also provided.&quot; data-og-host=&quot;play.google.com&quot; data-og-source-url=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&quot; data-og-url=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&amp;amp;hl=ko&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/czLd1P/hyDnjfJW0L/qDPYD8aFvfFMc98kyKiLK0/img.png?width=512&amp;amp;height=250&amp;amp;face=0_0_512_250,https://scrap.kakaocdn.net/dn/McaI6/hyDnmQ5H8S/TnzOISQwiEcJCDXG9Vbo71/img.png?width=600&amp;amp;height=300&amp;amp;face=0_0_600_300&quot;&gt;&lt;a href=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&amp;amp;hl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/czLd1P/hyDnjfJW0L/qDPYD8aFvfFMc98kyKiLK0/img.png?width=512&amp;amp;height=250&amp;amp;face=0_0_512_250,https://scrap.kakaocdn.net/dn/McaI6/hyDnmQ5H8S/TnzOISQwiEcJCDXG9Vbo71/img.png?width=600&amp;amp;height=300&amp;amp;face=0_0_600_300');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;Flutter Code Examples - Google Play 앱&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;Are you a beginner at Flutter? Check out the various features of Flutter through the demo. Source code for all demos is also provided.&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;play.google.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;hr style=&quot;margin: 20px auto 0px; border: none; cursor: pointer !important; z-index: 1; font-size: 0px; line-height: 0; height: 20px; background: url('../image/divider-line.svg') 0px -180px / 200px 200px no-repeat; width: 200px; color: #333333; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;blockquote class=&quot;tx-quote-tistory&quot;&gt;
&lt;p&gt;Flutter 강좌 시즌2 목록 :&amp;nbsp;&lt;a class=&quot;tx-link&quot; href=&quot;https://here4you.tistory.com/149&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://here4you.tistory.com/149&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;이번 시간은 강좌가 아닌 팁을 하나 소개한다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;최근 개발 중인 앱에서 File Picker가 필요해서 다음과 같은 플러그인들을 살펴보았다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;a href=&quot;https://pub.dev/packages/file_picker&quot;&gt;https://pub.dev/packages/file_picker&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1592289033472&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;website&quot; data-og-title=&quot;file_picker | Flutter Package&quot; data-og-description=&quot;A package that allows you to use a native file explorer to pick single or multiple absolute file paths, with extension filtering support.&quot; data-og-host=&quot;pub.dev&quot; data-og-source-url=&quot;https://pub.dev/packages/file_picker&quot; data-og-url=&quot;https://pub.dev/packages/file_picker&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/d1lkhT/hyGrr2qLsb/pHV0MOY4LCYKGsiylT5Q5k/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/fw8KN/hyGqfWPLNo/NLVMxMHm4quQpIKPgD3LMK/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/caxHbe/hyGqeczLE4/8vb0ZuMhQUPGoQB0mQjha1/img.png?width=2000&amp;amp;height=664&amp;amp;face=0_0_2000_664&quot;&gt;&lt;a href=&quot;https://pub.dev/packages/file_picker&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://pub.dev/packages/file_picker&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/d1lkhT/hyGrr2qLsb/pHV0MOY4LCYKGsiylT5Q5k/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/fw8KN/hyGqfWPLNo/NLVMxMHm4quQpIKPgD3LMK/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/caxHbe/hyGqeczLE4/8vb0ZuMhQUPGoQB0mQjha1/img.png?width=2000&amp;amp;height=664&amp;amp;face=0_0_2000_664');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;file_picker | Flutter Package&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;A package that allows you to use a native file explorer to pick single or multiple absolute file paths, with extension filtering support.&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;pub.dev&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;a href=&quot;https://pub.dev/packages/flutter_document_picker&quot;&gt;https://pub.dev/packages/flutter_document_picker&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1592289051853&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;website&quot; data-og-title=&quot;flutter_document_picker | Flutter Package&quot; data-og-description=&quot;Allows user pick a document. Picked document is copied to app temporary directory. Optionally allows pick document with specific extension only.&quot; data-og-host=&quot;pub.dev&quot; data-og-source-url=&quot;https://pub.dev/packages/flutter_document_picker&quot; data-og-url=&quot;https://pub.dev/packages/flutter_document_picker&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/PDh8K/hyGqgIc8Qi/BC4qJVTXbyklOOAVlZJZm1/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/keueL/hyGqh1qbNp/b1TzcEvqJRV47twwQ0fBwK/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640&quot;&gt;&lt;a href=&quot;https://pub.dev/packages/flutter_document_picker&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://pub.dev/packages/flutter_document_picker&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/PDh8K/hyGqgIc8Qi/BC4qJVTXbyklOOAVlZJZm1/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/keueL/hyGqh1qbNp/b1TzcEvqJRV47twwQ0fBwK/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;flutter_document_picker | Flutter Package&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;Allows user pick a document. Picked document is copied to app temporary directory. Optionally allows pick document with specific extension only.&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;pub.dev&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;문제는 별다른 오류 없이 빌드가 되고 설치까지 되는데, 장작 파일을 선택하면 앱이 비정상 종료되거나 파일의 경로값으로 null을 받는 문제가 있었다. 구글링을 해보니 꽤 많은 사람들이 정상 동작하지 않는다고 하고 있으나 나이스한 나이게 맞는 해결책은 없었다. 이 문제로 며칠을 고생했는데 결과적으로 원인은 따로 있었다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;처음 의심한 부분은 안드로이 플랫폼이 10으로 업그래이드 되면서 파일 시스템에 접근하는 방법이 변경되어 발생하는 것이라고 추측했었다. 이미 비스한 경험이 있었기 때문이나 아니었다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;a href=&quot;https://here4you.tistory.com/244&quot;&gt;https://here4you.tistory.com/244&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1592289234281&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Flutter 강좌 - [Tip] image_picker 플러그인을 이용해서 갤러리 이미지를 읽어오지 못할 때 해결 법 | Fai&quot; data-og-description=&quot;Flutter Code Examples 강좌를 추천합니다. 제 블로그에서 Flutter Code Examples 프로젝트를 시작합니다. Flutter의 다양한 예제를 소스코드와 실행화면으로 제공합니다. 또한 모든 예제는 Flutter Code Examples..&quot; data-og-host=&quot;here4you.tistory.com&quot; data-og-source-url=&quot;https://here4you.tistory.com/244&quot; data-og-url=&quot;https://here4you.tistory.com/244&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/caH5ok/hyGqaaagQD/5nb9s0XMcpKxBzeI5HyD0k/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/buyS1Y/hyGqg2w051/4c29D1W555zGxc4YZ9ETWk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/gTedS/hyGrCXbRHN/En3upkTmXHuZfGwXryorp1/img.jpg?width=765&amp;amp;height=765&amp;amp;face=245_215_460_449&quot;&gt;&lt;a href=&quot;https://here4you.tistory.com/244&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://here4you.tistory.com/244&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/caH5ok/hyGqaaagQD/5nb9s0XMcpKxBzeI5HyD0k/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/buyS1Y/hyGqg2w051/4c29D1W555zGxc4YZ9ETWk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/gTedS/hyGrCXbRHN/En3upkTmXHuZfGwXryorp1/img.jpg?width=765&amp;amp;height=765&amp;amp;face=245_215_460_449');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;Flutter 강좌 - [Tip] image_picker 플러그인을 이용해서 갤러리 이미지를 읽어오지 못할 때 해결 법 | Fai&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;Flutter Code Examples 강좌를 추천합니다. 제 블로그에서 Flutter Code Examples 프로젝트를 시작합니다. Flutter의 다양한 예제를 소스코드와 실행화면으로 제공합니다. 또한 모든 예제는 Flutter Code Examples..&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;here4you.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;그 외에 각종 퍼미션을 넣었다 뺐다 해봤었는데 이것으로도 해결되지 않았다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;그래서 플러그인 개발자의 Git Repository를 뒤져보기 시작했다. 결국 그 안에 답이 있었는데 너무 오래 멀리 돌아왔다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;결론부터 기술하자면, MainActivity의 &lt;span&gt;configureFlutterEngine 메소드에서 플로그인을 등록하는 부분을 삭제하면 해결된다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;span&gt;Android Studio를 이용해서 Flutter 프로젝트를 생성하면 다음과 같은 MainActivity 파일이 자동 생성된다. 그리고 &lt;span style=&quot;color: #333333;&quot;&gt;configureFlutterEngine&lt;span&gt; 메소드에서 생성된 플러그인을 플러터의 엔진에 등록하는 것으로 추측되는 코드가 실행된다. 정확한 구조는 본인도 잘 모른다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1592289515410&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.h4u.plugininstalltest;

import androidx.annotation.NonNull;
import io.flutter.embedding.android.FlutterActivity;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.plugins.GeneratedPluginRegistrant;

public class MainActivity extends FlutterActivity {
  @Override
  public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
    GeneratedPluginRegistrant.registerWith(flutterEngine);
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;다음으로 io.flutter.plugins 디렉토리에 자동 생성된 GeneratedPluginRegistrant.java 파일을 확인해 보자.&lt;/p&gt;
&lt;pre id=&quot;code_1592289668253&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package io.flutter.plugins;

import androidx.annotation.Keep;
import androidx.annotation.NonNull;
import io.flutter.embedding.engine.FlutterEngine;

/**
 * Generated file. Do not edit.
 * This file is generated by the Flutter tool based on the
 * plugins that support the Android platform.
 */
@Keep
public final class GeneratedPluginRegistrant {
  public static void registerWith(@NonNull FlutterEngine flutterEngine) {
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;MainActivity의 &lt;span&gt;configureFlutterEngine 메소드에서 수행된 내용과 동일한 기능을 수행하는 것으로 추측되는 코드가 자동 생성되어 있다. 아직 설치한 플러그인이 없으니 아무 내용이 없는 상태다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;span&gt;앱을 개발하면서 플러그인인들을 설치하면 설치된 플러그인의 내용이 이 파일에 자동으로 구성되게 된다. 주석으로도 적혀있지 않는가? 안드로이드 플랫폼을 지원하는 플러그인들을 기반으로 해서 자동으로 생성된 파일이므로 수정하지 말라고....&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;실제로 플러그인들을 추가하다보면 다음과 같이 파일의 내용이 자동으로 수정된다.&lt;/p&gt;
&lt;pre id=&quot;code_1592290019931&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package io.flutter.plugins;

import androidx.annotation.Keep;
import androidx.annotation.NonNull;

import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.embedding.engine.plugins.shim.ShimPluginRegistry;

/**
 * Generated file. Do not edit.
 * This file is generated by the Flutter tool based on the
 * plugins that support the Android platform.
 */
@Keep
public final class GeneratedPluginRegistrant {
    public static void registerWith(@NonNull FlutterEngine flutterEngine) {
        ShimPluginRegistry shimPluginRegistry = new ShimPluginRegistry(flutterEngine);
        com.github.yasukotelin.ext_storage.ExtStoragePlugin.registerWith(shimPluginRegistry.registrarFor(&quot;com.github.yasukotelin.ext_storage.ExtStoragePlugin&quot;));
        flutterEngine.getPlugins().add(new com.sidlatau.flutterdocumentpicker.FlutterDocumentPickerPlugin());
        io.github.ponnamkarthik.toast.fluttertoast.FluttertoastPlugin.registerWith(shimPluginRegistry.registrarFor(&quot;io.github.ponnamkarthik.toast.fluttertoast.FluttertoastPlugin&quot;));
        de.pdad.getip.GetIpPlugin.registerWith(shimPluginRegistry.registrarFor(&quot;de.pdad.getip.GetIpPlugin&quot;));
        flutterEngine.getPlugins().add(new io.flutter.plugins.packageinfo.PackageInfoPlugin());
        flutterEngine.getPlugins().add(new io.flutter.plugins.pathprovider.PathProviderPlugin());
        flutter.plugins.screen.screen.ScreenPlugin.registerWith(shimPluginRegistry.registrarFor(&quot;flutter.plugins.screen.screen.ScreenPlugin&quot;));
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;기존에는 이 부분(Android Java쪽 코드)은 신경쓰지 않았는데, 두 파일에서 플러그인이 플러터엔진에 두번 등록되면서 문제가 발생하는 것으로 추측된다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;아마도 일부 상태값을 가지는 플러그인의 경우 중복 등록에 의해 상태값을 일어버리면서 오류가 발생되는 것이 아닌가 싶다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;메인함수를 다음과 같이 중복호출되지 않도록 수정하면 플러그인이 정상 동작한다.&lt;/p&gt;
&lt;pre id=&quot;code_1592290289290&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.h4u.plugininstalltest;

import androidx.annotation.NonNull;

import io.flutter.embedding.android.FlutterActivity;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.plugins.GeneratedPluginRegistrant;

public class MainActivity extends FlutterActivity {
    @Override
    public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
//    GeneratedPluginRegistrant.registerWith(flutterEngine);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;며칠을 고생했는데 허무하게 해결되었다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Development/Flutter</category>
      <author>독행소년</author>
      <guid isPermaLink="true">https://here4you.tistory.com/266</guid>
      <comments>https://here4you.tistory.com/266#entry266comment</comments>
      <pubDate>Tue, 16 Jun 2020 15:52:55 +0900</pubDate>
    </item>
    <item>
      <title>Ubuntu에 Docker 설치 방법 | How to install Docker on Ubuntu</title>
      <link>https://here4you.tistory.com/265</link>
      <description>&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이번 시간에는 Lightsail의 Ubuntu 18.04에서 Docker를 설치하는 방법에 대해서 알아본다. 본 포스팅은 Docker 공신 문서를 참고하여 작성되었다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.docker.com/engine/install/ubuntu/&quot;&gt;https://docs.docker.com/engine/install/ubuntu/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1590455617115&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Install Docker Engine on Ubuntu&quot; data-og-description=&quot;To get started with Docker Engine on Ubuntu, make sure you meet the prerequisites, then install Docker. Prerequisites OS requirements To install Docker Engine, you need the 64-bit version of...&quot; data-og-host=&quot;docs.docker.com&quot; data-og-source-url=&quot;https://docs.docker.com/engine/install/ubuntu/&quot; data-og-url=&quot;https://docs.docker.com/engine/install/ubuntu/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/o3qFu/hyGbiS5wLa/GS1V789acEmudwwWZtO9C1/img.png?width=129&amp;amp;height=128&amp;amp;face=0_0_129_128,https://scrap.kakaocdn.net/dn/ninNO/hyGbrCuPto/OqDK2dvedVOzuIWGGqs6k0/img.png?width=950&amp;amp;height=500&amp;amp;face=0_0_950_500&quot;&gt;&lt;a href=&quot;https://docs.docker.com/engine/install/ubuntu/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.docker.com/engine/install/ubuntu/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/o3qFu/hyGbiS5wLa/GS1V789acEmudwwWZtO9C1/img.png?width=129&amp;amp;height=128&amp;amp;face=0_0_129_128,https://scrap.kakaocdn.net/dn/ninNO/hyGbrCuPto/OqDK2dvedVOzuIWGGqs6k0/img.png?width=950&amp;amp;height=500&amp;amp;face=0_0_950_500');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;Install Docker Engine on Ubuntu&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;To get started with Docker Engine on Ubuntu, make sure you meet the prerequisites, then install Docker. Prerequisites OS requirements To install Docker Engine, you need the 64-bit version of...&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;docs.docker.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;우분투 머신을 준비하기 위해 본인은 Lightsail에서 5불짜리 인스턴스를 하나 준비했다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Docker 설치전에 호스트 머신의 패지키를 업데이트하고 필요한 패키지들을 설치한다.(대부분 기본 설치되어 있다.)&lt;/p&gt;
&lt;pre id=&quot;code_1590455718119&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$&amp;gt; sudo apt-get update
$&amp;gt; sudo apt-get install apt-transport-https ca-certificates curl gnupg-agent software-properties-common&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Docker의 공식 GPG 키를 추가한다.&lt;/p&gt;
&lt;pre id=&quot;code_1590455849777&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$&amp;gt; curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;키가 등록되었는지 확인해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1590455990221&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ubuntu@here4you:~$ sudo apt-key fingerprint 0EBFCD88
pub   rsa4096 2017-02-22 [SCEA]
      9DC8 5822 9FC7 DD38 854A  E2D8 8D81 803C 0EBF CD88
uid           [ unknown] Docker Release (CE deb) &amp;lt;docker@docker.com&amp;gt;
sub   rsa4096 2017-02-22 [S]&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Docker의 저장소를 추가한다.&lt;/p&gt;
&lt;pre id=&quot;code_1593578246812&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$&amp;gt; sudo add-apt-repository &quot;deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Docker를 설치한다.&lt;/p&gt;
&lt;pre id=&quot;code_1590456509668&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$&amp;gt; sudo apt-get update
$&amp;gt; sudo apt-get install docker-ce docker-ce-cli containerd.io&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Docker 설치여부를 확인한다.&lt;/p&gt;
&lt;pre id=&quot;code_1590456695902&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ubuntu@here4you:~$ sudo docker -v
Docker version 19.03.9, build 9d988398e7
ubuntu@here4you:~$&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;버전 정보가 확인되면 정상 설치된 것이다.&lt;/p&gt;</description>
      <category>Development/Cloud computing</category>
      <author>독행소년</author>
      <guid isPermaLink="true">https://here4you.tistory.com/265</guid>
      <comments>https://here4you.tistory.com/265#entry265comment</comments>
      <pubDate>Tue, 26 May 2020 10:31:58 +0900</pubDate>
    </item>
    <item>
      <title>Flutter 강좌 - 플랫폼간 메소드 호출 #6 - Android에서 Flutter의 메소드 호출하기 | MethodChannel 사용법 | How to use MethodChannel</title>
      <link>https://here4you.tistory.com/264</link>
      <description>&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em; text-align: justify;&quot;&gt;&lt;b&gt;Flutter Code Examples 강좌를 추천합니다.&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;제 블로그에서 Flutter Code Examples 프로젝트를 시작합니다.&lt;/li&gt;
&lt;li&gt;Flutter의 다양한 예제를 소스코드와 실행화면으로 제공합니다.&lt;/li&gt;
&lt;li&gt;또한 모든 예제는 &lt;a href=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Flutter Code Examples&lt;/a&gt; 앱을 통해 테스트 가능합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;a href=&quot;https://here4you.tistory.com/category/Tutorial/Flutter%20with%20App&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Flutter Code Examples 강좌로 메뉴로 이동&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;a href=&quot;https://here4you.tistory.com/213&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Flutter Code Examples 강좌 목록 페이지로 이동&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;a href=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Flutter Code Examples 앱 설치 | Google Play Store로 이동&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1571794790206&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Flutter Code Examples - Google Play 앱&quot; data-og-description=&quot;Are you a beginner at Flutter? Check out the various features of Flutter through the demo. Source code for all demos is also provided.&quot; data-og-host=&quot;play.google.com&quot; data-og-source-url=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&quot; data-og-url=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&amp;amp;hl=ko&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/czLd1P/hyDnjfJW0L/qDPYD8aFvfFMc98kyKiLK0/img.png?width=512&amp;amp;height=250&amp;amp;face=0_0_512_250,https://scrap.kakaocdn.net/dn/McaI6/hyDnmQ5H8S/TnzOISQwiEcJCDXG9Vbo71/img.png?width=600&amp;amp;height=300&amp;amp;face=0_0_600_300&quot;&gt;&lt;a href=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&amp;amp;hl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/czLd1P/hyDnjfJW0L/qDPYD8aFvfFMc98kyKiLK0/img.png?width=512&amp;amp;height=250&amp;amp;face=0_0_512_250,https://scrap.kakaocdn.net/dn/McaI6/hyDnmQ5H8S/TnzOISQwiEcJCDXG9Vbo71/img.png?width=600&amp;amp;height=300&amp;amp;face=0_0_600_300');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;Flutter Code Examples - Google Play 앱&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;Are you a beginner at Flutter? Check out the various features of Flutter through the demo. Source code for all demos is also provided.&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;play.google.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;hr style=&quot;margin: 20px auto 0px; border: none; cursor: pointer !important; z-index: 1; font-size: 0px; line-height: 0; height: 20px; background: url('../image/divider-line.svg') 0px -180px / 200px 200px no-repeat; width: 200px; color: #333333; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;blockquote class=&quot;tx-quote-tistory&quot;&gt;
&lt;p&gt;Flutter 강좌 시즌2 목록 :&amp;nbsp;&lt;a class=&quot;tx-link&quot; href=&quot;https://here4you.tistory.com/149&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://here4you.tistory.com/149&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;이번 강좌에서는 Android에서 Flutter의 메소드를 호출하는 방법에 대해서 알아본다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;이번 강좌는 Flutter Plugin 템플릿 코드를 이용해서 작성되었다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;우선 실행화면을 보면서 컨셉을 이해해 보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;anigif.gif&quot; data-origin-width=&quot;389&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lsUHD/btqElWSeu7H/8I4kDfzDn3bwB3Vf3BWg9k/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lsUHD/btqElWSeu7H/8I4kDfzDn3bwB3Vf3BWg9k/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lsUHD/btqElWSeu7H/8I4kDfzDn3bwB3Vf3BWg9k/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/lsUHD/btqElWSeu7H/8I4kDfzDn3bwB3Vf3BWg9k/img.gif&quot; data-filename=&quot;anigif.gif&quot; data-origin-width=&quot;389&quot; data-origin-height=&quot;800&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;버튼을 클릭할 때마다 하단의 카운트 값이 1씩 증가하고 있다. 실제 동작과정은 다음과 같다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;- 버튼이 클릭되면 Flutter에서 Android로 getCount 메소드 호출을 요청한다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;- Android에서는 getCount 메소드 호출 요청이 수신되면 바로 응답하지 않고, &lt;span style=&quot;color: #333333;&quot;&gt;getCount&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;메소드를 호출한다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;- Android의 &lt;span style=&quot;color: #333333;&quot;&gt;getCount&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;메소드는 Android로 pushCount 메소드 호출을 요청하면서 count 값을 아규먼트로 전달한다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;기존의 강좌에서는 메소드 호출 요청이 수신되면 result 객체를 통해 바로 응답을 반환했지만, 이번에는 응답을 Android에서 Flutter로의 메소드 호출 요청으로 반환하고 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;소스 코드를 살펴보자.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Flutter의 화면 구성&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1590118350045&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:flutter/material.dart';
import 'package:methodchanneltest/methodchanneltest.dart';

void main() =&amp;gt; runApp(MyApp());

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() =&amp;gt; _MyAppState();
}

class _MyAppState extends State&amp;lt;MyApp&amp;gt; {
  // Android로부터 수신한 count값을 저장/출력하기 위한 필드
  int _count = 0;

  @override
  void initState() {
    super.initState();
    // MethodChannel 초기화, 요청 수신을 위한 핸들러 등록용
    Methodchanneltest.initMethodChannel(setCountCallback);
  }

  // Android로부터 수신한 count 값을 필드에 반영하기 위한 콜백
  void setCountCallback(int count) {
    setState(() {
      _count = count;
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Plugin example app'),
        ),
        body: Center(
          child: Column(
            children: &amp;lt;Widget&amp;gt;[
              RaisedButton(
                child: Text(&quot;getCount&quot;),
                onPressed: () {
                  Methodchanneltest.getCount();
                },
              ),
              Text(&quot;count: $_count&quot;)
            ],
          ),
        ),
      ),
    );
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;화면은 단순하게 구성되어 있다. 버튼이 클릭되면 getCount 메소드를 호출한다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Flutter의 메소드채널플러그인 구현&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1590119240492&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'dart:async';
import 'package:flutter/services.dart';

class Methodchanneltest {
  // 안드로이드와 메소드 호출 통신을 위한 메소드 채널
  static const MethodChannel _channel =
      const MethodChannel('methodchanneltest');

  // 안드로이드로부터 수신한 count 값을 갱신하기 위한 콜백
  static Function(int count) callback;

  // 메소드채널에 안드로이드로부터의 메소드 호출을 수신할 핸들러 등록
  static void initMethodChannel(void Function(int count) setCountCallback) {
    _channel.setMethodCallHandler(handler);
    callback = setCountCallback;
  }

  // 안드로이드로 카운트를 요청하기 위한 메소드
  static void getCount() async {
    await _channel.invokeMethod('getCount');
  }

  // 안드로이드로부터의 메소드 호출을 수신하는 핸들러
  static Future&amp;lt;dynamic&amp;gt; handler(MethodCall call) async {
    switch (call.method) {
      // count 값을 수신하는 메소드 호출인 경우
      case &quot;pushCount&quot;:
        var count = call.arguments;
        // 수신한 카운트 값을 콜백을 통해 화면에 갱신
        callback(count);
        break;
      default:
        throw (&quot;method not defined&quot;);
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;Android로부터의 메소드 호출 요청을 수신하기 위해 메소드채널에 핸들러를 등록해야 한다. 이를 위해 initMethodChannel 메소드가 초반에 호출되어야 하면, 수신된 카운트값을 갱신하기 위한 콜백도 아규먼트로 받고 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;getCount 메소드가 호출되면 채널을 통해 getCount 메소드 호출을 요청한다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;Android로부터의 메소드 호출 요청을 수신하는 핸들러에서 pushCount 메소드 호출 요청이 수신되면 count값을 받아서 콜백을 통해 화면을 갱신한다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Android의 메소드 호출 처리&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1590119453324&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.msoc.methodchanneltest;

import android.os.Handler;
import android.os.Looper;

import androidx.annotation.NonNull;

import java.util.Timer;
import java.util.TimerTask;

import io.flutter.embedding.engine.plugins.FlutterPlugin;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
import io.flutter.plugin.common.MethodChannel.Result;
import io.flutter.plugin.common.PluginRegistry.Registrar;

/**
 * MethodchanneltestPlugin
 */
public class MethodchanneltestPlugin implements FlutterPlugin, MethodCallHandler {

    static MethodChannel channel;
    Handler handler = new Handler(Looper.getMainLooper());
    Timer eventTimer = new Timer();
    int count = 0;

    // 메소드 채널 생성 및 핸들러 등록(AndroidX)
    @Override
    public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) {
        channel = new MethodChannel(flutterPluginBinding.getFlutterEngine().getDartExecutor(), &quot;methodchanneltest&quot;);
        channel.setMethodCallHandler(new MethodchanneltestPlugin());
    }

    // 메소드 채널 생성 및 핸들러 등록(non AndroidX)
    public static void registerWith(Registrar registrar) {
        channel = new MethodChannel(registrar.messenger(), &quot;methodchanneltest&quot;);
        channel.setMethodCallHandler(new MethodchanneltestPlugin());
    }

    // 플러터발 메소드 호출을 수신하는 핸들러
    @Override
    public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
        // getCount를 수신한 경우 fireEvent 메소드를 호출
        if (call.method.equals(&quot;getCount&quot;)) {
            getCount();
        } else {
            result.notImplemented();
        }
    }


    // pushCount 함수를 요청하면서 count 값을 전달
    void getCount() {

        // 현재 구현 처럼 onMethodCall에서 직접 getCount()를 호출하는 경우 아래와 같이 직접 pushCount를 호출 가능
        // channel.invokeMethod(&quot;pushCount&quot;, ++count);

        // onAttachedToEngine()나 registerWith()에서 getCount()를 호출하는 경우는
        // 아래와 같이 별도의 스레드로 분리해서 pushCount를 호출 가능
        handler.post(new Runnable() {
            @Override
            public void run() {
                channel.invokeMethod(&quot;pushCount&quot;, ++count);
            }
        });
    }

    @Override
    public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;onAttachedToEngine과 registerWith 메소드에서 메소드 채널을 생성하고, 핸들러를 등록한다. 두 함수에서 동일한 작업을 반복하는 이유는 AndroidX 사용 여부와 관련이 있다고 하는데, 이렇게 두번 등록하는 것을 추천하고 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;onMethodCall의 경우 기존과 동일하게 Flutter로부터의 메소드 호출 요청을 수신하고 처리하는 부분이다. getCount 호출 요청을 받은 경우 getCount 메소드를 호출한다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;getCount 메소드의 경우 메소드채널을 이용해서 Flutter의 pushCount 메소드 호출을 요청하면서 값이 1 증가된 count 값을 아규먼트로 전달하는 기능을 한다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;이 때 유의해야 할 점이 있다. 이번 구현의 경우 onMethodCall 핸들러 내부에서 getCount 메소드를 호출하고 있기 때문에 &lt;span&gt;channel.invokeMethod 메소드를 이용해서 바로 Flutter의 메소드를 호출할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;span&gt;하지만 &lt;span style=&quot;color: #333333;&quot;&gt;onAttachedToEngine와 같이 Android의 메인 스레드의 흐름안에서 &lt;span style=&quot;color: #333333;&quot;&gt;channel.invokeMethod 메소드를 직접 호출 하면 에러가 발생한다. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;이는 Android에서 핸들러를 이용해야하는 이유와 동일하다. 이 경우에는 핸들러를 이용해서 간접적으로 &lt;span style=&quot;color: #333333;&quot;&gt;channel.invokeMethod 메소드를 호출해야 한다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Development/Flutter</category>
      <author>독행소년</author>
      <guid isPermaLink="true">https://here4you.tistory.com/264</guid>
      <comments>https://here4you.tistory.com/264#entry264comment</comments>
      <pubDate>Fri, 22 May 2020 13:01:07 +0900</pubDate>
    </item>
    <item>
      <title>Git에서 특정 디렉토리를 관리 대상에서 제외하는 방법 | git rm 사용법</title>
      <link>https://here4you.tistory.com/263</link>
      <description>&lt;p&gt;git 저장소에 원치 않는 파일이나 디렉토리를 add한 경우, 관리 대상에서 제외하려면 git rm 명령을 사용한다.&lt;/p&gt;
&lt;pre id=&quot;code_1588824052152&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$&amp;gt; git rm [파일명 or 디렉토리]&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;하지만 이경우 로컬에 있는 파일까지 동시에 삭제된다고 한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;로컬의 파일은 유지하면서 git 저장소의 파일만 삭제하려면(관리 대상에서 제외) --cached 옵션을 추가한다.&lt;/p&gt;
&lt;pre id=&quot;code_1588824137090&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$&amp;gt; git rm --cached [파일 or 디렉토리]&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Development/ETC</category>
      <author>독행소년</author>
      <guid isPermaLink="true">https://here4you.tistory.com/263</guid>
      <comments>https://here4you.tistory.com/263#entry263comment</comments>
      <pubDate>Thu, 7 May 2020 13:02:22 +0900</pubDate>
    </item>
    <item>
      <title>Flutter 강좌 - 플랫폼간 메소드 호출 #5 - MethodChannel을 이용한 오브젝트 교환 방법 | How to exchange object using MethodChannel</title>
      <link>https://here4you.tistory.com/262</link>
      <description>&lt;p style=&quot;font-size: 1.12em; text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em; text-align: justify;&quot;&gt;&lt;b&gt;Flutter Code Examples 강좌를 추천합니다.&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;제 블로그에서 Flutter Code Examples 프로젝트를 시작합니다.&lt;/li&gt;
&lt;li&gt;Flutter의 다양한 예제를 소스코드와 실행화면으로 제공합니다.&lt;/li&gt;
&lt;li&gt;또한 모든 예제는 &lt;a href=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Flutter Code Examples&lt;/a&gt; 앱을 통해 테스트 가능합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;a href=&quot;https://here4you.tistory.com/category/Tutorial/Flutter%20with%20App&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Flutter Code Examples 강좌로 메뉴로 이동&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;a href=&quot;https://here4you.tistory.com/213&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Flutter Code Examples 강좌 목록 페이지로 이동&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;a href=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Flutter Code Examples 앱 설치 | Google Play Store로 이동&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1571794790206&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Flutter Code Examples - Google Play 앱&quot; data-og-description=&quot;Are you a beginner at Flutter? Check out the various features of Flutter through the demo. Source code for all demos is also provided.&quot; data-og-host=&quot;play.google.com&quot; data-og-source-url=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&quot; data-og-url=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&amp;amp;hl=ko&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/czLd1P/hyDnjfJW0L/qDPYD8aFvfFMc98kyKiLK0/img.png?width=512&amp;amp;height=250&amp;amp;face=0_0_512_250,https://scrap.kakaocdn.net/dn/McaI6/hyDnmQ5H8S/TnzOISQwiEcJCDXG9Vbo71/img.png?width=600&amp;amp;height=300&amp;amp;face=0_0_600_300&quot;&gt;&lt;a href=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&amp;amp;hl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/czLd1P/hyDnjfJW0L/qDPYD8aFvfFMc98kyKiLK0/img.png?width=512&amp;amp;height=250&amp;amp;face=0_0_512_250,https://scrap.kakaocdn.net/dn/McaI6/hyDnmQ5H8S/TnzOISQwiEcJCDXG9Vbo71/img.png?width=600&amp;amp;height=300&amp;amp;face=0_0_600_300');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;Flutter Code Examples - Google Play 앱&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;Are you a beginner at Flutter? Check out the various features of Flutter through the demo. Source code for all demos is also provided.&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;play.google.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;hr style=&quot;margin: 20px auto 0px; border: none; cursor: pointer !important; z-index: 1; font-size: 0px; line-height: 0; height: 20px; background: url('../image/divider-line.svg') 0px -180px / 200px 200px no-repeat; width: 200px; color: #333333; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;blockquote class=&quot;tx-quote-tistory&quot;&gt;
&lt;p&gt;Flutter 강좌 시즌2 목록 :&amp;nbsp;&lt;a class=&quot;tx-link&quot; href=&quot;https://here4you.tistory.com/149&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://here4you.tistory.com/149&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;이번 강좌에서는 MethodChannel을 이용해서 Flutter와 Android간의 메소드 호출을 요청할 때 파라미터와 아규먼트로 커스텀 오브젝트를 이용하는 방법에 대해서 알아본다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;이름, 나이, 성별로 구성되는 Human이라는 클래스의 인스턴스를 MethodChannel을 이용해 주고 받는 것이 목적이다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;b&gt;1. Class Model 정의&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Flutter용 Human Class Model&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1587895450758&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Human {
  static final String fName = &quot;name&quot;;
  static final String fAge = &quot;age&quot;;
  static final String fIsMale = &quot;isMale&quot;;

  String name;
  int age;
  bool isMale;

  Human({this.name, this.age, this.isMale});

  factory Human.fromMap(Map&amp;lt;dynamic, dynamic&amp;gt; snapshot) {
    return Human(
      name: snapshot[fName] as String,
      age: snapshot[fAge] as int,
      isMale: snapshot[fIsMale] as bool,
    );
  }

  Map&amp;lt;String, dynamic&amp;gt; toMap() {
    return {
      fName: name,
      fAge: age,
      fIsMale: isMale,
    };
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Android용 Human Class Model&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1587895485203&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.example.passobject;


import org.json.JSONException;
import org.json.JSONObject;

public class Human {
    static final String fName = &quot;name&quot;;
    static final String fAge = &quot;age&quot;;
    static final String fIsMale = &quot;isMale&quot;;


    String name;
    int age;
    boolean isMale;

    public Human(String name, int age, boolean isMale) {
        this.name = name;
        this.age = age;
        this.isMale = isMale;
    }

    static public Human fromMap(Object obj) {

        try {
            JSONObject jo = new JSONObject(obj.toString());
            return new Human(jo.getString(fName), jo.getInt(fAge), jo.getBoolean(fIsMale));
        } catch (JSONException e) {
            e.printStackTrace();
        }
        return null;
    }

    public String toMap() {
        JSONObject jo = new JSONObject();
        try {
            jo.put(fName, name);
            jo.put(fAge, age);
            jo.put(fIsMale, isMale);
        } catch (JSONException e) {
            e.printStackTrace();
        }

        return jo.toString();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;플랫폼별 클래스의 구성은 위와 같이 그 구성이 동일하다. 그리고 오브젝트의 내용을 Map으로 변환하거나 Map으로부터 다시 오브젝트로 구성하기 위해 toMap 메소드와 fromMap 메소드를 각각 플랫폼에 맞게 구현하였다. 이는 MethodChannel로 파라미터를 전달할 때 커스텀 오브젝트를 직접 전달할 수 없기 때문에 맵 형태로 전달하고 다시 오브젝트로 재구성하기 위함이다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;b&gt;2. 오브젝트 교환&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;text-align: justify;&quot;&gt;Flutter -&amp;gt; Android&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1587898513960&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Passobject {
  static const MethodChannel _channel = const MethodChannel('passobject');

  static Future&amp;lt;void&amp;gt; remoteMethodCall() async {
    Human sender = Human(name: &quot;Park&quot;, age: 40, isMale: true);
    print(&quot;[F][Sender] ${sender.toMap()}&quot;);

    var result = await _channel.invokeMethod(&quot;methodCall&quot;, sender.toMap());
    Human receiver = Human.fromMap(jsonDecode(result));
    print(&quot;[F][Receiver] ${receiver.toMap()}&quot;);
    return result;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Android -&amp;gt; Flutter&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1587898557574&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    @Override
    public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
        if (call.method.equals(&quot;methodCall&quot;)) {

            Human sender = Human.fromMap(call.arguments());
            System.out.println(&quot;[A][Sender] &quot; + sender.toMap());

            Human receiver = new Human(&quot;Lee&quot;, 38, false);
            System.out.println(&quot;[A][Receiver] &quot; + receiver.toMap());
            result.success(receiver.toMap());
        } else {
            result.notImplemented();
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;Flutter와 Android간의 커스텀 오브젝트를 아규먼트/파라미터로 이용하는 방식은 위와 같다. 오브젝트가 MethodChannel을 통과하기 위해서는 Map 형태로 변화되어야 하기 때문 각 플랫폼 별로 인스턴스를 맵으로 변환하고, 수신한 맵을 다시 인스턴스로 구성하여 이용하게 된다.&lt;/p&gt;</description>
      <category>Development/Flutter</category>
      <author>독행소년</author>
      <guid isPermaLink="true">https://here4you.tistory.com/262</guid>
      <comments>https://here4you.tistory.com/262#entry262comment</comments>
      <pubDate>Sun, 26 Apr 2020 20:07:18 +0900</pubDate>
    </item>
    <item>
      <title>Flutter 강좌 - 플랫폼간 메소드 호출 #4 - Flutter에서 네이티브 API 호출하는 방법 | How to call native APIs in Flutter</title>
      <link>https://here4you.tistory.com/261</link>
      <description>&lt;p style=&quot;font-size: 1.12em; text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em; text-align: justify;&quot;&gt;&lt;b&gt;Flutter Code Examples 강좌를 추천합니다.&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;제 블로그에서 Flutter Code Examples 프로젝트를 시작합니다.&lt;/li&gt;
&lt;li&gt;Flutter의 다양한 예제를 소스코드와 실행화면으로 제공합니다.&lt;/li&gt;
&lt;li&gt;또한 모든 예제는 &lt;a href=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Flutter Code Examples&lt;/a&gt; 앱을 통해 테스트 가능합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;a href=&quot;https://here4you.tistory.com/category/Tutorial/Flutter%20with%20App&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Flutter Code Examples 강좌로 메뉴로 이동&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;a href=&quot;https://here4you.tistory.com/213&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Flutter Code Examples 강좌 목록 페이지로 이동&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;a href=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Flutter Code Examples 앱 설치 | Google Play Store로 이동&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1571794790206&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Flutter Code Examples - Google Play 앱&quot; data-og-description=&quot;Are you a beginner at Flutter? Check out the various features of Flutter through the demo. Source code for all demos is also provided.&quot; data-og-host=&quot;play.google.com&quot; data-og-source-url=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&quot; data-og-url=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&amp;amp;hl=ko&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/czLd1P/hyDnjfJW0L/qDPYD8aFvfFMc98kyKiLK0/img.png?width=512&amp;amp;height=250&amp;amp;face=0_0_512_250,https://scrap.kakaocdn.net/dn/McaI6/hyDnmQ5H8S/TnzOISQwiEcJCDXG9Vbo71/img.png?width=600&amp;amp;height=300&amp;amp;face=0_0_600_300&quot;&gt;&lt;a href=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&amp;amp;hl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/czLd1P/hyDnjfJW0L/qDPYD8aFvfFMc98kyKiLK0/img.png?width=512&amp;amp;height=250&amp;amp;face=0_0_512_250,https://scrap.kakaocdn.net/dn/McaI6/hyDnmQ5H8S/TnzOISQwiEcJCDXG9Vbo71/img.png?width=600&amp;amp;height=300&amp;amp;face=0_0_600_300');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;Flutter Code Examples - Google Play 앱&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;Are you a beginner at Flutter? Check out the various features of Flutter through the demo. Source code for all demos is also provided.&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;play.google.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;hr style=&quot;margin: 20px auto 0px; border: none; cursor: pointer !important; z-index: 1; font-size: 0px; line-height: 0; height: 20px; background: url('../image/divider-line.svg') 0px -180px / 200px 200px no-repeat; width: 200px; color: #333333; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;blockquote class=&quot;tx-quote-tistory&quot;&gt;
&lt;p&gt;Flutter 강좌 시즌2 목록 :&amp;nbsp;&lt;a class=&quot;tx-link&quot; href=&quot;https://here4you.tistory.com/149&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://here4you.tistory.com/149&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;이번 강좌에서는 Flutter에서 네이티브 라이브러리를 호출하는 방법에 대해서 알아본다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;이 기법은 복잡하거나 어려운 방법이 아니라 지난 강좌에서 진행한 방법을 단순히 순차적으로 합친 것 뿐이다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;Flutter에서 Android를 호출할 때에는 MethodChannel을 이용하고, Android에서 다시 NDK를 이용해 네이티브 라이브러리를 호출하여 그 결과를 반대 경로로 전달해주는 것 뿐이다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;그림1.png&quot; data-origin-width=&quot;567&quot; data-origin-height=&quot;330&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wawK8/btqDzkBjXa5/akvUNRwk2R6yjtOEjq659k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wawK8/btqDzkBjXa5/akvUNRwk2R6yjtOEjq659k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wawK8/btqDzkBjXa5/akvUNRwk2R6yjtOEjq659k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwawK8%2FbtqDzkBjXa5%2FakvUNRwk2R6yjtOEjq659k%2Fimg.png&quot; data-filename=&quot;그림1.png&quot; data-origin-width=&quot;567&quot; data-origin-height=&quot;330&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;우선 앱을 통해 개발할 기능의 컨셉을 확인해 보자. 지난 강좌의 프로젝트를 이용해서 추가 개발한 것이다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;anigif.gif&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b6zBW5/btqDADArfeo/Ye4hcQZ7u0KDTOtsejKcUK/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b6zBW5/btqDADArfeo/Ye4hcQZ7u0KDTOtsejKcUK/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b6zBW5/btqDADArfeo/Ye4hcQZ7u0KDTOtsejKcUK/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/b6zBW5/btqDADArfeo/Ye4hcQZ7u0KDTOtsejKcUK/img.gif&quot; data-filename=&quot;anigif.gif&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;800&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;앱의 실행 화면을 보면, 지난 강좌에서 이용한 MethodChannel을 이용해서 int 타입의 변수 a와 b에 값을 입력하고, 덧셈을 수행하는 addition 메소드와, 뺄셈을 수행하는 subtraction 메소드를 추가로 구현하고 그 결과를 수신해서 화면에 출력하는 앱이다. 그리고 두 메소드의 실제 구현을 C 언어로 작성되어 네이티브 라이브러리로 생성될 것이며, Android의 NDK를 이용해서 호출되는 방식이다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;b&gt;1. Flutter에서 Android 메소드 호출&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;메소드 호출의 시작점인 Flutter 단의 main.dart는 다음과 같이 작성한다.&lt;/p&gt;
&lt;pre id=&quot;code_1587518964844&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

void main() =&amp;gt; runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Method Channel Test',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() =&amp;gt; _MyHomePageState();
}

class _MyHomePageState extends State&amp;lt;MyHomePage&amp;gt; {
  static const MethodChannel _channel =
      const MethodChannel('com.example.methodchanneltest');

  String _platformVersion = 'Unknown';

  int a, b;
  int additionResult, subtractionResult;

  Future&amp;lt;String&amp;gt; getPlatformVersion() async {
    final String version = await _channel.invokeMethod('getPlatformVersion');
    return version;
  }

  Future&amp;lt;int&amp;gt; addition(int a, int b) async {
    return await _channel.invokeMethod(&quot;addition&quot;, &amp;lt;String, dynamic&amp;gt;{
      &quot;a&quot;: a,
      &quot;b&quot;: b,
    });
  }

  Future&amp;lt;int&amp;gt; subtraction(int a, int b) async {
    return await _channel.invokeMethod(&quot;subtraction&quot;, &amp;lt;String, dynamic&amp;gt;{
      &quot;a&quot;: a,
      &quot;b&quot;: b,
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(&quot;Method Channel Test&quot;),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: &amp;lt;Widget&amp;gt;[
            RaisedButton(
              child: Text(&quot;Get Platform Version&quot;),
              onPressed: () async {
                String result = await getPlatformVersion();
                setState(() {
                  _platformVersion = result;
                });
              },
            ),
            Text(_platformVersion),
            Container(
              margin: const EdgeInsets.symmetric(vertical: 10, horizontal: 10),
              child: Row(
                mainAxisAlignment: MainAxisAlignment.spaceAround,
                children: &amp;lt;Widget&amp;gt;[
                  Text(&quot;a = &quot;),
                  Container(
                    width: 50,
                    child: TextField(
                      onChanged: (value) {
                        setState(() {
                          a = int.parse(value);
                        });
                      },
                      textAlign: TextAlign.center,
                    ),
                  ),
                  Text(&quot;b = &quot;),
                  Container(
                    width: 50,
                    child: TextField(
                      onChanged: (value) {
                        setState(() {
                          b = int.parse(value);
                        });
                      },
                      textAlign: TextAlign.center,
                    ),
                  )
                ],
              ),
            ),
            Container(
              child: Row(
                mainAxisAlignment: MainAxisAlignment.spaceAround,
                children: &amp;lt;Widget&amp;gt;[
                  RaisedButton(
                    child: Text(&quot;invoke addition&quot;),
                    onPressed: () async {
                      int result = await addition(a, b);
                      setState(() {
                        additionResult = result;
                      });
                    },
                  ),
                  Text(&quot; = $additionResult&quot;)
                ],
              ),
            ),
            Container(
              child: Row(
                mainAxisAlignment: MainAxisAlignment.spaceAround,
                children: &amp;lt;Widget&amp;gt;[
                  RaisedButton(
                    child: Text(&quot;invoke subtraction&quot;),
                    onPressed: () async {
                      int result = await subtraction(a, b);
                      setState(() {
                        subtractionResult = result;
                      });
                    },
                  ),
                  Text(&quot; = $subtractionResult&quot;)
                ],
              ),
            )
          ],
        ),
      ),
    );
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;기존에 구현한 getPlatformVersion 메소드 외에 추가로 addition과 subtraction을 하고 이 메소드들을 이용하는 UI 위젯들도 추가했다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;MethodChannel의 인스턴스를 통해 Android 측 메소드를 호출하는 방식은 다음과 같다. 파라미터는 Map 형식으로 입력할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1587519073105&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  Future&amp;lt;int&amp;gt; addition(int a, int b) async {
    return await _channel.invokeMethod(&quot;addition&quot;, &amp;lt;String, dynamic&amp;gt;{
      &quot;a&quot;: a,
      &quot;b&quot;: b,
    });
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;b&gt;2. Android에서 Flutter의 호출 처리&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;Adnroid 측의 MainActivity는 다음과 같이 작성한다.&lt;/p&gt;
&lt;pre id=&quot;code_1587519261468&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.example.methodchanneltest;

import android.os.Build;

import androidx.annotation.NonNull;

import io.flutter.embedding.android.FlutterActivity;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugins.GeneratedPluginRegistrant;

public class MainActivity extends FlutterActivity {

    // load native lib
    static {
        System.loadLibrary(&quot;native-lib&quot;);
    }

    @Override
    public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
        GeneratedPluginRegistrant.registerWith(flutterEngine);
        final MethodChannel channel = new MethodChannel(flutterEngine.getDartExecutor(), &quot;com.example.methodchanneltest&quot;);
        channel.setMethodCallHandler(handler);
    }

    // MethodCallHandler
    private MethodChannel.MethodCallHandler handler = (methodCall, result) -&amp;gt; {
        if (methodCall.method.equals(&quot;getPlatformVersion&quot;)) {
            result.success(&quot;Android Version: &quot; + Build.VERSION.RELEASE);
        } else if (methodCall.method.equals(&quot;addition&quot;)) {
            result.success(addition(methodCall.argument(&quot;a&quot;), methodCall.argument(&quot;b&quot;)));
        } else if (methodCall.method.equals(&quot;subtraction&quot;)) {
            result.success(subtraction(methodCall.argument(&quot;a&quot;), methodCall.argument(&quot;b&quot;)));
        } else {
            result.notImplemented();
        }
    };


    // 추가된 네이티브 API
    public native int addition(int a, int b);

    public native int subtraction(int a, int b);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;우선 기존 MethodCallHandler 처리 로직에 addition과 subtraction에 대한 처리 로직을 추가한다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;Map 형태로 파라미터를 전달하고,아규먼트로 수신하는 방법은 다음과 같다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Flutter에서 메소드 호출시 파라미터 전달 방법&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1587519498172&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  Future&amp;lt;int&amp;gt; addition(int a, int b) async {
    return await _channel.invokeMethod(&quot;addition&quot;, &amp;lt;String, dynamic&amp;gt;{
      &quot;a&quot;: a,
      &quot;b&quot;: b,
    });
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Android에서는 메소드 호출 수신시 아규먼트 수신 방법&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1587519633817&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if (methodCall.method.equals(&quot;addition&quot;)) {
    int a = methodCall.argument(&quot;a&quot;);
    int b = methodCall.argument(&quot;b&quot;);
    result.success(addition(a,b));
} &lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;다음으로는 NDK를 이용하기 위한 구현이다. 소스 상단에 네이티브 라이브러리를 참조하기 위해서 소스 상단에서는 네이티브 라이브러리(native-lib)를 로드한다. 다음으로 네이티브 라이브러를 호출하기 위한 메소드 addtion과 subtraction을 선언한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333;&quot;&gt;3. 네이티브 라이브러리 추가 구현&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;android/app/src/main 디렉토리에 cpp 디렉토리를 추가한 후 native-lib.cpp 파일을 생성한 후 다음과 같이 작성한다. 기존 강좌에서 실습한 코드와 동일하나 함수명안에 패키지 이름 부분만 현재 패키지명으로 수정된 것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1587519860107&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;jni.h&amp;gt;
#include &amp;lt;string&amp;gt;
#include &quot;calculator.cpp&quot;

extern &quot;C&quot; JNIEXPORT jint JNICALL
Java_com_example_methodchanneltest_MainActivity_addition(
        JNIEnv *env,
        jobject /* this */,
        jint a,
        jint b) {

    return addition(a, b);
}

extern &quot;C&quot; JNIEXPORT jint JNICALL
Java_com_example_methodchanneltest_MainActivity_subtraction(
        JNIEnv *env,
        jobject /* this */,
        jint a,
        jint b) {

    return subtraction(a, b);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;실제 덧셈과 뺄셈을 수행하는 addition 함수와 subtraction 함수를 구현하기 위해 calculator.cpp 파일을 생성한 후 다음과 같이 작성한다. 기존 강좌의 내용과 동일하다.&lt;/p&gt;
&lt;pre id=&quot;code_1587520029221&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int addition(int a, int b) {
    return a + b;
}

int subtraction(int a, int b) {
    return a - b;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;네이티브 라이브러리를 컴파일하기 위한 규칙을 기술하기 위한 CMakeLists.txt 파일을 생성하여 다음과 같이 작성한다. 역시 기존 강좌 내용과 동일하다.&lt;/p&gt;
&lt;pre id=&quot;code_1587520089267&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;cmake_minimum_required(VERSION 3.4.1)

add_library( # Sets the name of the library.
             native-lib

             # Sets the library as a shared library.
             SHARED

             # Provides a relative path to your source file(s).
             native-lib.cpp )

find_library( # Sets the name of the path variable.
              log-lib

              # Specifies the name of the NDK library that
              # you want CMake to locate.
              log )

target_link_libraries( # Specifies the target library.
                       native-lib

                       # Links the target library to the log library
                       # included in the NDK.
                       ${log-lib} )&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;마지막으로 네이티브 라이브러리를 빌드하고 앱 프로젝트에 포함되게 하기 위해 android/build.gradle 파일의 android 항목 마지막에 다음의 내용을 추가한다.&lt;/p&gt;
&lt;pre id=&quot;code_1587520181768&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    externalNativeBuild {
        cmake {
            path &quot;src/main/cpp/CMakeLists.txt&quot;
            version &quot;3.10.2&quot;
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;이것으로 구현을 완료되었다. 앱을 실행해서 동작 과정을 다시 테스트해보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;anigif.gif&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6V0ut/btqDyN4PslW/Afw0fcH73MOuDGGezCNcn0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6V0ut/btqDyN4PslW/Afw0fcH73MOuDGGezCNcn0/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6V0ut/btqDyN4PslW/Afw0fcH73MOuDGGezCNcn0/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/6V0ut/btqDyN4PslW/Afw0fcH73MOuDGGezCNcn0/img.gif&quot; data-filename=&quot;anigif.gif&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;800&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;이번 강좌에서는 Flutter에서 네이티브 라이브러리를 호출하는 방법에 대해서 알아보았다. 이로서 Flutter - Android -C++ 간 자유로운 호출이 가능해졌다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Development/Flutter</category>
      <author>독행소년</author>
      <guid isPermaLink="true">https://here4you.tistory.com/261</guid>
      <comments>https://here4you.tistory.com/261#entry261comment</comments>
      <pubDate>Wed, 22 Apr 2020 10:53:07 +0900</pubDate>
    </item>
    <item>
      <title>Flutter 강좌 - 플랫폼간 메소드 호출 #3 - Flutter에서 안드로이드 메소드 호출하기 | MethodChannel 사용법 | How to use MethodChannel</title>
      <link>https://here4you.tistory.com/260</link>
      <description>&lt;p style=&quot;font-size: 1.12em; text-align: justify;&quot;&gt;&lt;b&gt;Flutter Code Examples 강좌를 추천합니다.&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;제 블로그에서 Flutter Code Examples 프로젝트를 시작합니다.&lt;/li&gt;
&lt;li&gt;Flutter의 다양한 예제를 소스코드와 실행화면으로 제공합니다.&lt;/li&gt;
&lt;li&gt;또한 모든 예제는 &lt;a href=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Flutter Code Examples&lt;/a&gt; 앱을 통해 테스트 가능합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;a href=&quot;https://here4you.tistory.com/category/Tutorial/Flutter%20with%20App&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Flutter Code Examples 강좌로 메뉴로 이동&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;a href=&quot;https://here4you.tistory.com/213&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Flutter Code Examples 강좌 목록 페이지로 이동&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;a href=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Flutter Code Examples 앱 설치 | Google Play Store로 이동&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1571794790206&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Flutter Code Examples - Google Play 앱&quot; data-og-description=&quot;Are you a beginner at Flutter? Check out the various features of Flutter through the demo. Source code for all demos is also provided.&quot; data-og-host=&quot;play.google.com&quot; data-og-source-url=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&quot; data-og-url=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&amp;amp;hl=ko&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/czLd1P/hyDnjfJW0L/qDPYD8aFvfFMc98kyKiLK0/img.png?width=512&amp;amp;height=250&amp;amp;face=0_0_512_250,https://scrap.kakaocdn.net/dn/McaI6/hyDnmQ5H8S/TnzOISQwiEcJCDXG9Vbo71/img.png?width=600&amp;amp;height=300&amp;amp;face=0_0_600_300&quot;&gt;&lt;a href=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&amp;amp;hl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/czLd1P/hyDnjfJW0L/qDPYD8aFvfFMc98kyKiLK0/img.png?width=512&amp;amp;height=250&amp;amp;face=0_0_512_250,https://scrap.kakaocdn.net/dn/McaI6/hyDnmQ5H8S/TnzOISQwiEcJCDXG9Vbo71/img.png?width=600&amp;amp;height=300&amp;amp;face=0_0_600_300');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;Flutter Code Examples - Google Play 앱&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;Are you a beginner at Flutter? Check out the various features of Flutter through the demo. Source code for all demos is also provided.&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;play.google.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;hr style=&quot;margin: 20px auto 0px; border: none; cursor: pointer !important; z-index: 1; font-size: 0px; line-height: 0; height: 20px; background: url('../image/divider-line.svg') 0px -180px / 200px 200px no-repeat; width: 200px; color: #333333; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;blockquote class=&quot;tx-quote-tistory&quot;&gt;
&lt;p&gt;Flutter 강좌 시즌2 목록 :&amp;nbsp;&lt;a class=&quot;tx-link&quot; href=&quot;https://here4you.tistory.com/149&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://here4you.tistory.com/149&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;지난 강좌에서는 안드로이드에서 NDK를 이용해 네이티브 라이브러리를 작성하고 호출하는 방법에 대해서 알아봤다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;이번 강좌에너는 Flutter에서 안드로이드 메소드를 호출하는 방법에 대해서 알아본다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;Flutter에서 안드로이드의 메소드를 호출할 때 사용되는 것이 MethodChannel이다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;그림2.png&quot; data-origin-width=&quot;566&quot; data-origin-height=&quot;180&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bRWq8d/btqDA6uxt6V/aYorJbXSnUPRA3TIkWCqG0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bRWq8d/btqDA6uxt6V/aYorJbXSnUPRA3TIkWCqG0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bRWq8d/btqDA6uxt6V/aYorJbXSnUPRA3TIkWCqG0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbRWq8d%2FbtqDA6uxt6V%2FaYorJbXSnUPRA3TIkWCqG0%2Fimg.png&quot; data-filename=&quot;그림2.png&quot; data-origin-width=&quot;566&quot; data-origin-height=&quot;180&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;b&gt;1. Flutter Application 프로젝트 생성&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;새로운 Flutter Application 프로젝트를 선택한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;이미지 17.png&quot; data-origin-width=&quot;902&quot; data-origin-height=&quot;676&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdJKrQ/btqDzEFhP8I/rJtKZD6dWRgto7b0wAVFjK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdJKrQ/btqDzEFhP8I/rJtKZD6dWRgto7b0wAVFjK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdJKrQ/btqDzEFhP8I/rJtKZD6dWRgto7b0wAVFjK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdJKrQ%2FbtqDzEFhP8I%2FrJtKZD6dWRgto7b0wAVFjK%2Fimg.png&quot; data-filename=&quot;이미지 17.png&quot; data-origin-width=&quot;902&quot; data-origin-height=&quot;676&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;프로젝트명으로 method_channel_test를 입력한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;이미지 18.png&quot; data-origin-width=&quot;902&quot; data-origin-height=&quot;676&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcW6I6/btqDyE0dMd0/9Ju5nNpPOHLT0cc9ESFnKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcW6I6/btqDyE0dMd0/9Ju5nNpPOHLT0cc9ESFnKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcW6I6/btqDyE0dMd0/9Ju5nNpPOHLT0cc9ESFnKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbcW6I6%2FbtqDyE0dMd0%2F9Ju5nNpPOHLT0cc9ESFnKK%2Fimg.png&quot; data-filename=&quot;이미지 18.png&quot; data-origin-width=&quot;902&quot; data-origin-height=&quot;676&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;기본 패키지명으로 프로젝트 생성을 완료한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;이미지 19.png&quot; data-origin-width=&quot;902&quot; data-origin-height=&quot;676&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/F8ZJL/btqDxuquH3L/qHpZ4pwIUkbFT1g3iXsr41/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/F8ZJL/btqDxuquH3L/qHpZ4pwIUkbFT1g3iXsr41/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/F8ZJL/btqDxuquH3L/qHpZ4pwIUkbFT1g3iXsr41/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FF8ZJL%2FbtqDxuquH3L%2FqHpZ4pwIUkbFT1g3iXsr41%2Fimg.png&quot; data-filename=&quot;이미지 19.png&quot; data-origin-width=&quot;902&quot; data-origin-height=&quot;676&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;b&gt;2. main.dart 작성&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;main.dart를 다음과 같이 작성한다.&lt;/p&gt;
&lt;pre id=&quot;code_1587382410384&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

void main() =&amp;gt; runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Method Channel Test',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() =&amp;gt; _MyHomePageState();
}

class _MyHomePageState extends State&amp;lt;MyHomePage&amp;gt; {
  static const MethodChannel _channel =
      const MethodChannel('com.example.methodchanneltest');

  String _platformVersion = 'Unknown';

  Future&amp;lt;String&amp;gt; getPlatformVersion() async {
    final String version = await _channel.invokeMethod('getPlatformVersion');
    return version;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(&quot;Method Channel Test&quot;),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: &amp;lt;Widget&amp;gt;[
            RaisedButton(
              child: Text(&quot;Get Platform Version&quot;),
              onPressed: () async {
                String result = await getPlatformVersion();
                setState(() {
                  _platformVersion = result;
                });
              },
            ),
            Text(_platformVersion),
          ],
        ),
      ),
    );
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;Flutter에서의 MethodChannel은 다음과 같이 생성한다. MethodChannel의 생성자에 파라미터로 메소드채널의 이름을 입력하는데 Android측의 MethodChannel 생성시에도 같은 이름으로 부여해야 통신이 가능해 진다. 더욱이 다른 앱들간에도 간섭이 발생할 수 있으므로 패키지명으로 이름을 부여하는 것을 추천한다.&lt;/p&gt;
&lt;pre id=&quot;code_1587382469694&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  static const MethodChannel _channel =
      const MethodChannel('com.example.methodchanneltest');&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;실제 안드로이드의 메소드를 호출하는 방법은 다음과 같다. 위에서 생성한 채널의 인스턴스를 통해 invokeMethod 메소드를 호출한다. 이 때 파라미터로 호출할 메소드의 이름을 입력한다.&lt;/p&gt;
&lt;pre id=&quot;code_1587382639785&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  Future&amp;lt;String&amp;gt; getPlatformVersion() async {
    final String version = await _channel.invokeMethod('getPlatformVersion');
    return version;
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;b&gt;3. MainActivity.java 작성&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;Flutter로부터 메소드 호출을 받는 안드로이드 측의 소스코드는 다음과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1587382808141&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.example.methodchanneltest;

import android.os.Build;

import androidx.annotation.NonNull;

import io.flutter.embedding.android.FlutterActivity;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugins.GeneratedPluginRegistrant;

public class MainActivity extends FlutterActivity {

    @Override
    public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
        GeneratedPluginRegistrant.registerWith(flutterEngine);
        final MethodChannel channel = new MethodChannel(flutterEngine.getDartExecutor(), &quot;com.example.methodchanneltest&quot;);
        channel.setMethodCallHandler(handler);
    }

    private MethodChannel.MethodCallHandler handler = (methodCall, result) -&amp;gt; {
        if (methodCall.method.equals(&quot;getPlatformVersion&quot;)) {
            result.success(&quot;Android Version: &quot; + Build.VERSION.RELEASE);
        } else {
            result.notImplemented();
        }
    };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;MethodChannel의 생성은 다음과 같이 한다. Flutter와 동일한 채널의 이름을 할당해야 한다. 또한 Flutter로부터의 메소드 호출 이벤트를 수신할 MethodCallHandler를 채널에 등록한다.&lt;/p&gt;
&lt;pre id=&quot;code_1587382846323&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;        final MethodChannel channel = new MethodChannel(flutterEngine.getDartExecutor(), &quot;com.example.methodchanneltest&quot;);
        channel.setMethodCallHandler(handler);&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;채널에 등록할 MethodCallHandler는 다음과 같이 구현할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1587382924698&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    private MethodChannel.MethodCallHandler handler = (methodCall, result) -&amp;gt; {
        if (methodCall.method.equals(&quot;getPlatformVersion&quot;)) {
            result.success(&quot;Android Version: &quot; + Build.VERSION.RELEASE);
        } else {
            result.notImplemented();
        }
    };&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;람다 형식으로, 메소드호출을 수신할 methodCall과 결과를 응답할 result 아규먼트를 입력받는다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;methodCall의 method를 파싱하여 실제 호출할 메소드(혹은 변수)를 확인할 수 있으며, result를 이용해 호출의 성공이나 에러, 미구현들을 반환할 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;b&gt;4. 앱 실행 결과&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;실행 결과는 다음과 같다. 화면상의 버튼을 클릭하면 안드로이드측의 getPlatformVersion이란 이름의 메소드 호출이 전달되고, 안드로이드에서는 해당 디바이스의 안드로이드 빌드 버전 정보를 반환한다. 최종적으로 다음과 같이 안드로이드 버전 정보를 출력하게 된다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;anigif.gif&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/maw5C/btqDzC8zTkw/K5cjtKoz1bDDs9KhKbHQGk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/maw5C/btqDzC8zTkw/K5cjtKoz1bDDs9KhKbHQGk/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/maw5C/btqDzC8zTkw/K5cjtKoz1bDDs9KhKbHQGk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/maw5C/btqDzC8zTkw/K5cjtKoz1bDDs9KhKbHQGk/img.gif&quot; data-filename=&quot;anigif.gif&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;800&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;이번 강좌에서는 MethodChannel을 이용해서 Flutter로부터 Android로 메소드 호출 요청을 전달하고 그 결과를 반환하는 방법에 대해서 알아봤다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;사족으로, 우리가 흔히 pub.dev 사이트에서 검색해서 이용하는 Flutter 패키지(플러그인)의 상당수가 이런식으로 안드로이드의 네이티브 메소드를 호출하는 기능을 구현한 것이다. Flutter만으로는 플랫폼 특화된 기능을 이용할 수 없기 때문이다. 실제 이번 강좌에서 활용한 소스코드 역시 Android Studio의 Plugin 프로젝트의 템플릿 코드를 참고해서 작성한 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;이번 강좌까지 진행하면서, 안드로이드와 네이티브 라이브러리간 함수 호출과, 플러터와 안드로이드간의 메소드 호출의 방법에 대해서 알아봤다. 그렇다면 Flutter에서 시작한 호출이 네이티브 라이브러리까지 전달되어 반환될 순 없을까? 있다. 지금까지 강좌에서 실습한 방법을 머지하면 가능하다. 다음 강좌에서는 Flutter에서 네이티브 라이브러리까지 함수 호출을 전달하는 방법에 대해서 알아본다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Development/Flutter</category>
      <author>독행소년</author>
      <guid isPermaLink="true">https://here4you.tistory.com/260</guid>
      <comments>https://here4you.tistory.com/260#entry260comment</comments>
      <pubDate>Mon, 20 Apr 2020 18:07:20 +0900</pubDate>
    </item>
    <item>
      <title>Flutter 강좌 - 플랫폼간 메소드 호출 #2 - 네이티브 API 개발 | How to develop a native API using NDK</title>
      <link>https://here4you.tistory.com/258</link>
      <description>&lt;p style=&quot;font-size: 1.12em;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot;&gt;&lt;b&gt;Flutter Code Examples 강좌를 추천합니다.&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;제 블로그에서 Flutter Code Examples 프로젝트를 시작합니다.&lt;/li&gt;
&lt;li&gt;Flutter의 다양한 예제를 소스코드와 실행화면으로 제공합니다.&lt;/li&gt;
&lt;li&gt;또한 모든 예제는 &lt;a href=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Flutter Code Examples&lt;/a&gt; 앱을 통해 테스트 가능합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&quot;https://here4you.tistory.com/category/Tutorial/Flutter%20with%20App&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Flutter Code Examples 강좌로 메뉴로 이동&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://here4you.tistory.com/213&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Flutter Code Examples 강좌 목록 페이지로 이동&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Flutter Code Examples 앱 설치 | Google Play Store로 이동&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1571794790206&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Flutter Code Examples - Google Play 앱&quot; data-og-description=&quot;Are you a beginner at Flutter? Check out the various features of Flutter through the demo. Source code for all demos is also provided.&quot; data-og-host=&quot;play.google.com&quot; data-og-source-url=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&quot; data-og-url=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&amp;amp;hl=ko&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/czLd1P/hyDnjfJW0L/qDPYD8aFvfFMc98kyKiLK0/img.png?width=512&amp;amp;height=250&amp;amp;face=0_0_512_250,https://scrap.kakaocdn.net/dn/McaI6/hyDnmQ5H8S/TnzOISQwiEcJCDXG9Vbo71/img.png?width=600&amp;amp;height=300&amp;amp;face=0_0_600_300&quot;&gt;&lt;a href=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&amp;amp;hl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/czLd1P/hyDnjfJW0L/qDPYD8aFvfFMc98kyKiLK0/img.png?width=512&amp;amp;height=250&amp;amp;face=0_0_512_250,https://scrap.kakaocdn.net/dn/McaI6/hyDnmQ5H8S/TnzOISQwiEcJCDXG9Vbo71/img.png?width=600&amp;amp;height=300&amp;amp;face=0_0_600_300');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;Flutter Code Examples - Google Play 앱&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;Are you a beginner at Flutter? Check out the various features of Flutter through the demo. Source code for all demos is also provided.&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;play.google.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;hr style=&quot;margin: 20px auto 0px; border: none; cursor: pointer !important; z-index: 1; font-size: 0px; line-height: 0; height: 20px; background: url('../image/divider-line.svg') 0px -180px / 200px 200px no-repeat; width: 200px; color: #333333; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;blockquote class=&quot;tx-quote-tistory&quot;&gt;
&lt;p&gt;Flutter 강좌 시즌2 목록 :&amp;nbsp;&lt;a class=&quot;tx-link&quot; href=&quot;https://here4you.tistory.com/149&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://here4you.tistory.com/149&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;지난 강좌에서는 Android Studio가 제공하는 Native C++ 프로젝트를 통해 NDK의 동작 방식에 대해서 알아보았다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;이번 강좌에서는 그 연장선으로 안드로이드에서 호출 가능한 네이티브 API를 개발해본다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;지난 강좌에서 살펴본 안드로이드 프로젝트를 계속 사용한다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;b&gt;1. 네이티브 API 추가&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;네이티브 API를 추가하기 위해 새로운 cpp 파일(calculator.cpp)을 app/src/main/cpp 디렉토리에 추가한다.&lt;/p&gt;
&lt;pre id=&quot;code_1587360195695&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int addition(int a, int b) {
    return a + b;
}

int subtraction(int a, int b) {
    return a - b;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;간단한 덧셈과 뺄셈을 제공하는 함수를 추가했다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;b&gt;2. JNI 인터페이스 추가&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;기존의 native-lib.cpp 파일에 추가한 두 함수의 JNI 인터페이스를 추가한다.&lt;/p&gt;
&lt;pre id=&quot;code_1587360267693&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;jni.h&amp;gt;
#include &amp;lt;string&amp;gt;
#include &quot;calculator.cpp&quot;

extern &quot;C&quot; JNIEXPORT jstring JNICALL
Java_com_example_ndk_1test_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    std::string hello = &quot;Hello from C++&quot;;
    return env-&amp;gt;NewStringUTF(hello.c_str());
}

extern &quot;C&quot; JNIEXPORT jint JNICALL
Java_com_example_ndk_1test_MainActivity_addition(
        JNIEnv *env,
        jobject /* this */,
        jint a,
        jint b) {

    return addition(a, b);
}

extern &quot;C&quot; JNIEXPORT jint JNICALL
Java_com_example_ndk_1test_MainActivity_subtraction(
        JNIEnv *env,
        jobject /* this */,
        jint a,
        jint b) {

    return subtraction(a, b);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;b&gt;3. MainActivity 파일에 native 메소드 추가&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;MainActivity에 native 메소드를 다음과 같이 추가하고, onCreate 메소드에서 호출하는 테스트 코드를 작성한다.&lt;/p&gt;
&lt;pre id=&quot;code_1587360313467&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.example.ndk_test;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    // Used to load the 'native-lib' library on application startup.
    static {
        System.loadLibrary(&quot;native-lib&quot;);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Example of a call to a native method
        TextView tv = findViewById(R.id.sample_text);
        tv.setText(stringFromJNI());

        System.out.println(&quot; :::::::::::::::::::: &quot;);
        int additionResult = addition(10, 5);
        System.out.println(&quot;10 + 5 = &quot; + additionResult);

        System.out.println(&quot; :::::::::::::::::::: &quot;);
        int subtractionResult = subtraction(10, 5);
        System.out.println(&quot;10 - 5 = &quot; + subtractionResult);
    }

    /**
     * A native method that is implemented by the 'native-lib' native library,
     * which is packaged with this application.
     */
    public native String stringFromJNI();

    public native int addition(int a, int b);

    public native int subtraction(int a, int b);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;b&gt;4. 실행 결과&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;다음과 같이 덧셈과 뺄셈의 결과가 출력되는 성공한 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;이미지 8.png&quot; data-origin-width=&quot;854&quot; data-origin-height=&quot;472&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Ccvra/btqDzEkJnJu/cLpgD7zu46oKVQUhpwSzb0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Ccvra/btqDzEkJnJu/cLpgD7zu46oKVQUhpwSzb0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Ccvra/btqDzEkJnJu/cLpgD7zu46oKVQUhpwSzb0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCcvra%2FbtqDzEkJnJu%2FcLpgD7zu46oKVQUhpwSzb0%2Fimg.png&quot; data-filename=&quot;이미지 8.png&quot; data-origin-width=&quot;854&quot; data-origin-height=&quot;472&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;지금까지 두 번의 강좌를 통해 일반적인 안드로이드 프로젝트에서 네이티브 라이브러를 호출하는 방법과 네이티브 API를 추가하는 방법에 대해 알아보았다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;다음 강좌에서는 Android와 Flutter간 메소드 호출 방법에 대해서 알아본다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Development/Flutter</category>
      <author>독행소년</author>
      <guid isPermaLink="true">https://here4you.tistory.com/258</guid>
      <comments>https://here4you.tistory.com/258#entry258comment</comments>
      <pubDate>Mon, 20 Apr 2020 14:30:21 +0900</pubDate>
    </item>
    <item>
      <title>Flutter 강좌 - 플랫폼간 메소드 호출 #1 - 안드로이드에서 네이티브 API 호출 | How to call native API in Android</title>
      <link>https://here4you.tistory.com/257</link>
      <description>&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot;&gt;&lt;b&gt;Flutter Code Examples 강좌를 추천합니다.&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;제 블로그에서 Flutter Code Examples 프로젝트를 시작합니다.&lt;/li&gt;
&lt;li&gt;Flutter의 다양한 예제를 소스코드와 실행화면으로 제공합니다.&lt;/li&gt;
&lt;li&gt;또한 모든 예제는 &lt;a href=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Flutter Code Examples&lt;/a&gt; 앱을 통해 테스트 가능합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&quot;https://here4you.tistory.com/category/Tutorial/Flutter%20with%20App&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Flutter Code Examples 강좌로 메뉴로 이동&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://here4you.tistory.com/213&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Flutter Code Examples 강좌 목록 페이지로 이동&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Flutter Code Examples 앱 설치 | Google Play Store로 이동&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1571794790206&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Flutter Code Examples - Google Play 앱&quot; data-og-description=&quot;Are you a beginner at Flutter? Check out the various features of Flutter through the demo. Source code for all demos is also provided.&quot; data-og-host=&quot;play.google.com&quot; data-og-source-url=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&quot; data-og-url=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&amp;amp;hl=ko&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/czLd1P/hyDnjfJW0L/qDPYD8aFvfFMc98kyKiLK0/img.png?width=512&amp;amp;height=250&amp;amp;face=0_0_512_250,https://scrap.kakaocdn.net/dn/McaI6/hyDnmQ5H8S/TnzOISQwiEcJCDXG9Vbo71/img.png?width=600&amp;amp;height=300&amp;amp;face=0_0_600_300&quot;&gt;&lt;a href=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&amp;amp;hl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/czLd1P/hyDnjfJW0L/qDPYD8aFvfFMc98kyKiLK0/img.png?width=512&amp;amp;height=250&amp;amp;face=0_0_512_250,https://scrap.kakaocdn.net/dn/McaI6/hyDnmQ5H8S/TnzOISQwiEcJCDXG9Vbo71/img.png?width=600&amp;amp;height=300&amp;amp;face=0_0_600_300');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;Flutter Code Examples - Google Play 앱&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;Are you a beginner at Flutter? Check out the various features of Flutter through the demo. Source code for all demos is also provided.&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;play.google.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;hr style=&quot;margin: 20px auto 0px; border: none; cursor: pointer !important; z-index: 1; font-size: 0px; line-height: 0; height: 20px; background: url('../image/divider-line.svg') 0px -180px / 200px 200px no-repeat; width: 200px; color: #333333; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;blockquote class=&quot;tx-quote-tistory&quot;&gt;
&lt;p&gt;Flutter 강좌 시즌2 목록 :&amp;nbsp;&lt;a class=&quot;tx-link&quot; href=&quot;https://here4you.tistory.com/149&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://here4you.tistory.com/149&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;이번 강좌부터는 Flutter에서 네이티브 코드를 사용하는 방법에 대해서 알아본다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;이번 강좌는 첫번째 강좌로, Flutter가 아닌 일반 Android 프로젝트에서 NDK를 이용해서 C/C++ API를 호출하는 방법에 대해서 알아본다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;그림1.png&quot; data-origin-width=&quot;217&quot; data-origin-height=&quot;233&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tP8WO/btqDyEli2l2/n1KCZWtZ4ufk8vqyV1j07K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tP8WO/btqDyEli2l2/n1KCZWtZ4ufk8vqyV1j07K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tP8WO/btqDyEli2l2/n1KCZWtZ4ufk8vqyV1j07K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtP8WO%2FbtqDyEli2l2%2Fn1KCZWtZ4ufk8vqyV1j07K%2Fimg.png&quot; data-filename=&quot;그림1.png&quot; data-origin-width=&quot;217&quot; data-origin-height=&quot;233&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;Android Studio에서 Native C++ 프로젝트를 생성한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;이미지 1.png&quot; data-origin-width=&quot;902&quot; data-origin-height=&quot;676&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VppTF/btqDyD0UIzD/eaweS2jSsxMqt18AvbaZyK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VppTF/btqDyD0UIzD/eaweS2jSsxMqt18AvbaZyK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VppTF/btqDyD0UIzD/eaweS2jSsxMqt18AvbaZyK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVppTF%2FbtqDyD0UIzD%2FeaweS2jSsxMqt18AvbaZyK%2Fimg.png&quot; data-filename=&quot;이미지 1.png&quot; data-origin-width=&quot;902&quot; data-origin-height=&quot;676&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;프로젝트 이름을 설정한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;이미지 2.png&quot; data-origin-width=&quot;902&quot; data-origin-height=&quot;676&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/z1nYw/btqDx6hZguy/VkX5ZSkyd9zjodWTD98BVk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/z1nYw/btqDx6hZguy/VkX5ZSkyd9zjodWTD98BVk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/z1nYw/btqDx6hZguy/VkX5ZSkyd9zjodWTD98BVk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fz1nYw%2FbtqDx6hZguy%2FVkX5ZSkyd9zjodWTD98BVk%2Fimg.png&quot; data-filename=&quot;이미지 2.png&quot; data-origin-width=&quot;902&quot; data-origin-height=&quot;676&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;기본 툴체인으로 설정하고 Finish를 선택한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;이미지 3.png&quot; data-origin-width=&quot;902&quot; data-origin-height=&quot;676&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d182fw/btqDxtY277L/v4K4YtmG0lOYDwU6jOvJok/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d182fw/btqDxtY277L/v4K4YtmG0lOYDwU6jOvJok/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d182fw/btqDxtY277L/v4K4YtmG0lOYDwU6jOvJok/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd182fw%2FbtqDxtY277L%2Fv4K4YtmG0lOYDwU6jOvJok%2Fimg.png&quot; data-filename=&quot;이미지 3.png&quot; data-origin-width=&quot;902&quot; data-origin-height=&quot;676&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;프로젝트 생성이 완료되면 바로 앱을 실행해 보자.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;Screenshot_1587350286.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;2160&quot; width=&quot;300&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwhg30/btqDzjnoq9c/Osw49Y9NUUGYBu7aC7riUk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwhg30/btqDzjnoq9c/Osw49Y9NUUGYBu7aC7riUk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwhg30/btqDzjnoq9c/Osw49Y9NUUGYBu7aC7riUk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbwhg30%2FbtqDzjnoq9c%2FOsw49Y9NUUGYBu7aC7riUk%2Fimg.png&quot; data-filename=&quot;Screenshot_1587350286.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;2160&quot; width=&quot;300&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&quot;Hello from C++&quot;이란 문구가 출력되는 것을 확인할 수 있다. 이 문구는 우리가 작성한 것이 아니라 프로젝트 생성이 자동으로 작성된 코드 템플릿이다. 출력되는 내용으로 짐작하면, 이미 C++로 작성된 API가 호출되어 안드로이드 앱에 출력되고 있는 것으로 보인다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;그럼 소스를 좀 살펴보자.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;MainActivity.java의 구현 내용은 다음과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1587350505006&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.example.ndk_test;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    // Used to load the 'native-lib' library on application startup.
    static {
        System.loadLibrary(&quot;native-lib&quot;);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Example of a call to a native method
        TextView tv = findViewById(R.id.sample_text);
        tv.setText(stringFromJNI());
    }

    /**
     * A native method that is implemented by the 'native-lib' native library,
     * which is packaged with this application.
     */
    public native String stringFromJNI();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;System.loadLibrary를 이용해서 native-lib라는 네이티브 라이브러를 로딩하였고, stringFromJNI 메소드를 통해 화면에 문구를 출력하고 있다. stringFromJNI 메소드에 native라는 키워드가 붙어있은 것으로 보아 이 메소드가 native-lib 안에 있는 네이티브 API를 호출한 것으로 보인다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;우선 native-lib가 어디 있는지부터 확인해 보자. Android Studio의 워크스페이스를 프로젝트로 변경한 후 app/build/intermediates/cmak/debug/obj 디렉토리의 내용을 확인해 보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;이미지 4.png&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;547&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Dn0gi/btqDzkGDp6b/GeKaLrP8XZa0LqlQUOMtYK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Dn0gi/btqDzkGDp6b/GeKaLrP8XZa0LqlQUOMtYK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Dn0gi/btqDzkGDp6b/GeKaLrP8XZa0LqlQUOMtYK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDn0gi%2FbtqDzkGDp6b%2FGeKaLrP8XZa0LqlQUOMtYK%2Fimg.png&quot; data-filename=&quot;이미지 4.png&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;547&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;각 아키텍쳐별로 libnative-lib.so 라는 파일들이 존재한다. 이 so 파일들이 MainActivity.java에서 로드한 native-lib에 해당한다. 상위에 cmake 디렉토리가 존재하는 것으로 보아 camke 툴체인을 이용해서 컴파일된 것으로 유추할 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;여기서 잠깐 정리하자.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;MainActivity에서 사용한 stringFromJNI라는 네이티브 메소드는 cmake 툴체인으로 컴파일된 libnative-lib.so 라이브러리(native-lib) 내부의 API를 호출하고 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;그럼 다음으로 libnative-lib.so가 어떻게 생성되었는지 알아보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;이미지 5.png&quot; data-origin-width=&quot;399&quot; data-origin-height=&quot;407&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7ngJn/btqDzC8bb6Z/Dg1exdWJSTJFQVquwpkMRK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7ngJn/btqDzC8bb6Z/Dg1exdWJSTJFQVquwpkMRK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7ngJn/btqDzC8bb6Z/Dg1exdWJSTJFQVquwpkMRK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7ngJn%2FbtqDzC8bb6Z%2FDg1exdWJSTJFQVquwpkMRK%2Fimg.png&quot; data-filename=&quot;이미지 5.png&quot; data-origin-width=&quot;399&quot; data-origin-height=&quot;407&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;app/src/main 디렉토리를 살펴보면, MainActivity.java 파일이 존재하는 java 디렉토리와 동일한 수준으로 cpp 디렉토리가 존재하고 있고 그 디렉토래내에 native-lib.cpp 파일과 CMakeLists.txt 파일이 존재한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;native-lib.cpp 파일은 Native C++ 프로젝트 생성 시 자동으로 생성되는 템플릿 코드로 stringFromJNI 함수가 존재한다.&lt;/p&gt;
&lt;pre id=&quot;code_1587357290050&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;jni.h&amp;gt;
#include &amp;lt;string&amp;gt;

extern &quot;C&quot; JNIEXPORT jstring JNICALL
Java_com_example_ndk_1test_MainActivity_stringFromJNI(
        JNIEnv* env,
        jobject /* this */) {
    std::string hello = &quot;Hello from C++&quot;;
    return env-&amp;gt;NewStringUTF(hello.c_str());
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;내용을 보면 &quot;Hello from C++&quot; 문자열을 반환하는 기능을 가지고 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;함수의 구조를 조금 더 살펴보면,&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;JIN를 통해 호출되는 함수로 노출하기 위해서는 다음과 같이 함수를 선언한다. JNICALL 키워드 앞에는 리턴타입이 들어간다.&lt;/p&gt;
&lt;pre id=&quot;code_1587357902032&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;extern &quot;C&quot; JNIEXPORT jstring JNICALL&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;그리고 함수명은 다음과 같이 선언한다. &lt;span style=&quot;color: #333333;&quot;&gt;Java_패키지명_호출하는 Activity명_함수명 형태로 선언한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1587357934087&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Java_com_example_ndk_1test_MainActivity_stringFromJNI&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;다음으로 CMakeLists.txt 파일을 살펴보자.&lt;/p&gt;
&lt;pre id=&quot;code_1587358177983&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;cmake_minimum_required(VERSION 3.4.1)

add_library( # Sets the name of the library.
             native-lib

             # Sets the library as a shared library.
             SHARED

             # Provides a relative path to your source file(s).
             native-lib.cpp )

find_library( # Sets the name of the path variable.
              log-lib

              # Specifies the name of the NDK library that
              # you want CMake to locate.
              log )

target_link_libraries( # Specifies the target library.
                       native-lib

                       # Links the target library to the log library
                       # included in the NDK.
                       ${log-lib} )&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;add_library 항목을 살펴보면 native-lib.cpp 파일을 공유라이브러리로 설정하고 그 이름을 native-lib로 한다는 내용이다. MainActivity에서 로드한 native-lib가 native-lib.cpp 파일의 컴파일 결과물임을 확인할 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;여기서도 잠시 정리하자.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;MainActivity에서 로그하여 사용한 native-lib라는 라이브러리는 native-lib.cpp 파일의 컴파일 결과물이며, cmake 툴체인을 통해 공유라이브러리형태로 컴파일되고 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;자 마지막으로 native-lib 라이브러리가 어떻게 컴파일 되는지 확인해 보자.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;app/build.gradle 파일을 살펴보자.&lt;/p&gt;
&lt;pre id=&quot;code_1587358523743&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apply plugin: 'com.android.application'

android {
    compileSdkVersion 29


    defaultConfig {
        applicationId &quot;com.example.ndk_test&quot;
        minSdkVersion 21
        targetSdkVersion 29
        versionCode 1
        versionName &quot;1.0&quot;

        testInstrumentationRunner &quot;androidx.test.runner.AndroidJUnitRunner&quot;

        externalNativeBuild {
            cmake {
                cppFlags &quot;&quot;
            }
        }
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }

    externalNativeBuild {
        cmake {
            path &quot;src/main/cpp/CMakeLists.txt&quot;
            version &quot;3.10.2&quot;
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])

    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test.ext:junit:1.1.1'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;app/build.gradle 파일은 안드로이드 프로젝트(Flutter 프로젝트도 동일)를 빌드하는 규칙을 정의하고 있다. 일반적인 안드로이드 프로젝트의 build.gradle 파일과 비교해보면 android 항목 안에 externalNativeBuild 서브 항목이 추가되어 있는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;내용을 살펴보면, src/main/cpp/CMakeLists.txt 파일에 정의된 규칙을 참고해서 외부 네이티브 빌드를 진행하라는 의미 이다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;만약 일반 안드로이드 프로젝트를 개발하던 중 네이티브 코드를 추가하여 네이티브 라이브러리를 이용하고자 않다면, 위에서 살펴본 것 처럼 cpp 디렉토리를 생성하여 cpp파일과 CMakeLists.txt 파일을 추가한 후 build.gradle 파일에 externalNativeBuild 항목을 추가해주면 된다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;이번 강좌에서는 Android Studio에서 제공하는 Native C++ 프로젝트를 통해 NDK를 이용하여 네이티브 라이브러가 호출되는 구조에 대해서 살펴보았다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;다음 강좌에서는 네이티브 API를 추가해 보고, 안드로이드에서 호출하는 방법에 대해서 실습해보도록 한다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Development/Flutter</category>
      <author>독행소년</author>
      <guid isPermaLink="true">https://here4you.tistory.com/257</guid>
      <comments>https://here4you.tistory.com/257#entry257comment</comments>
      <pubDate>Mon, 20 Apr 2020 11:57:00 +0900</pubDate>
    </item>
    <item>
      <title>Flutter Example - TCP Socket Client</title>
      <link>https://here4you.tistory.com/256</link>
      <description>&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1586834862555&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'dart:io';

import 'package:flutter/material.dart';
import 'package:get_ip/get_ip.dart';
import 'package:shared_preferences/shared_preferences.dart';

SocketClientState pageState;

class SocketClient extends StatefulWidget {
  @override
  SocketClientState createState() {
    pageState = SocketClientState();
    return pageState;
  }
}

class SocketClientState extends State&amp;lt;SocketClient&amp;gt; {
  final scaffoldKey = GlobalKey&amp;lt;ScaffoldState&amp;gt;();

  String localIP = &quot;&quot;;
  int port = 9000;
  List&amp;lt;MessageItem&amp;gt; items = List&amp;lt;MessageItem&amp;gt;();

  TextEditingController ipCon = TextEditingController();
  TextEditingController msgCon = TextEditingController();

  Socket clientSocket;

  @override
  void initState() {
    super.initState();
    getIP();
    WidgetsBinding.instance.addPostFrameCallback((_) {
      _loadServerIP();
    });
  }

  @override
  void dispose() {
    disconnectFromServer();
    super.dispose();
  }

  void getIP() async {
    var ip = await GetIp.ipAddress;
    setState(() {
      localIP = ip;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        key: scaffoldKey,
        appBar: AppBar(title: Text(&quot;Socket Client&quot;)),
        body: Column(
          children: &amp;lt;Widget&amp;gt;[
            ipInfoArea(),
            connectArea(),
            messageListArea(),
            submitArea(),
          ],
        ));
  }

  Widget ipInfoArea() {
    return Card(
      child: ListTile(
        dense: true,
        leading: Text(&quot;IP&quot;),
        title: Text(localIP),
      ),
    );
  }

  Widget connectArea() {
    return Card(
      child: ListTile(
        dense: true,
        leading: Text(&quot;Server IP&quot;),
        title: TextField(
          controller: ipCon,
          decoration: InputDecoration(
              contentPadding:
                  const EdgeInsets.symmetric(vertical: 10, horizontal: 10),
              isDense: true,
              enabledBorder: OutlineInputBorder(
                borderRadius: BorderRadius.all(Radius.circular(5)),
                borderSide: BorderSide(color: Colors.grey[300]),
              ),
              focusedBorder: OutlineInputBorder(
                borderRadius: BorderRadius.all(Radius.circular(5)),
                borderSide: BorderSide(color: Colors.grey[400]),
              ),
              filled: true,
              fillColor: Colors.grey[50]),
        ),
        trailing: RaisedButton(
          child: Text((clientSocket != null) ? &quot;Disconnect&quot; : &quot;Connect&quot;),
          onPressed:
              (clientSocket != null) ? disconnectFromServer : connectToServer,
        ),
      ),
    );
  }

  Widget messageListArea() {
    return Expanded(
      child: ListView.builder(
          reverse: true,
          itemCount: items.length,
          itemBuilder: (context, index) {
            MessageItem item = items[index];
            return Container(
              alignment: (item.owner == localIP)
                  ? Alignment.centerRight
                  : Alignment.centerLeft,
              child: Container(
                margin: const EdgeInsets.symmetric(vertical: 5, horizontal: 10),
                padding:
                    const EdgeInsets.symmetric(vertical: 10, horizontal: 10),
                decoration: BoxDecoration(
                    borderRadius: BorderRadius.circular(10),
                    color: (item.owner == localIP)
                        ? Colors.blue[100]
                        : Colors.grey[200]),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: &amp;lt;Widget&amp;gt;[
                    Text(
                      (item.owner == localIP) ? &quot;Client&quot; : &quot;Server&quot;,
                      style: TextStyle(fontWeight: FontWeight.bold),
                    ),
                    Text(
                      item.content,
                      style: TextStyle(fontSize: 18),
                    ),
                  ],
                ),
              ),
            );
          }),
    );
  }

  Widget submitArea() {
    return Card(
      child: ListTile(
        title: TextField(
          controller: msgCon,
        ),
        trailing: IconButton(
          icon: Icon(Icons.send),
          color: Colors.blue,
          disabledColor: Colors.grey,
          onPressed: (clientSocket != null) ? submitMessage : null,
        ),
      ),
    );
  }

  void connectToServer() async {
    print(&quot;Destination Address: ${ipCon.text}&quot;);
    _storeServerIP();

    Socket.connect(ipCon.text, port, timeout: Duration(seconds: 5))
        .then((socket) {
      setState(() {
        clientSocket = socket;
      });

      showSnackBarWithKey(
          &quot;Connected to ${socket.remoteAddress.address}:${socket.remotePort}&quot;);
      socket.listen(
        (onData) {
          print(String.fromCharCodes(onData).trim());
          setState(() {
            items.insert(
                0,
                MessageItem(clientSocket.remoteAddress.address,
                    String.fromCharCodes(onData).trim()));
          });
        },
        onDone: onDone,
        onError: onError,
      );
    }).catchError((e) {
      showSnackBarWithKey(e.toString());
    });
  }

  void onDone() {
    showSnackBarWithKey(&quot;Connection has terminated.&quot;);
    disconnectFromServer();
  }

  void onError(e) {
    print(&quot;onError: $e&quot;);
    showSnackBarWithKey(e.toString());
    disconnectFromServer();
  }

  void disconnectFromServer() {
    print(&quot;disconnectFromServer&quot;);

    clientSocket.close();
    setState(() {
      clientSocket = null;
    });
  }

  void sendMessage(String message) {
    clientSocket.write(&quot;$message\n&quot;);
  }



  void _storeServerIP() async {
    SharedPreferences sp = await SharedPreferences.getInstance();
    sp.setString(&quot;serverIP&quot;, ipCon.text);
  }

  void _loadServerIP() async {
    SharedPreferences sp = await SharedPreferences.getInstance();
    setState(() {
      ipCon.text = sp.getString(&quot;serverIP&quot;);
    });
  }

  void submitMessage() {
    if (msgCon.text.isEmpty) return;
    setState(() {
      items.insert(0, MessageItem(localIP, msgCon.text));
    });
    sendMessage(msgCon.text);
    msgCon.clear();
  }

  showSnackBarWithKey(String message) {
    scaffoldKey.currentState
      ..hideCurrentSnackBar()
      ..showSnackBar(SnackBar(
        content: Text(message),
        action: SnackBarAction(
          label: 'Done',
          onPressed: (){},
        ),
      ));
  }
}

class MessageItem {
  String owner;
  String content;

  MessageItem(this.owner, this.content);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;anigif.gif&quot; data-origin-width=&quot;369&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OOMJZ/btqDpf0rY3r/PG8AkIixaTMG8aPEr6byRK/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OOMJZ/btqDpf0rY3r/PG8AkIixaTMG8aPEr6byRK/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OOMJZ/btqDpf0rY3r/PG8AkIixaTMG8aPEr6byRK/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/OOMJZ/btqDpf0rY3r/PG8AkIixaTMG8aPEr6byRK/img.gif&quot; data-filename=&quot;anigif.gif&quot; data-origin-width=&quot;369&quot; data-origin-height=&quot;800&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://here4you.tistory.com/213&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;▶ Go to Table of Contents | 강의 목차로 이동&lt;/a&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;p style=&quot;font-size: 0.94em;&quot;&gt;&lt;i&gt;※ This example is also available in the &lt;a href=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Flutter Code Examples&lt;/a&gt; app. | 본 예제는 &lt;a href=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Flutter Code Examples&lt;/a&gt; 앱에서도 제공됩니다.&lt;/i&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1571728595725&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Flutter Code Examples - Google Play 앱&quot; data-og-description=&quot;Are you a beginner at Flutter? Check out the various features of Flutter through the demo. Source code for all demos is also provided.&quot; data-og-host=&quot;play.google.com&quot; data-og-source-url=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&quot; data-og-url=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&amp;amp;hl=ko&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cKyXFQ/hyDk1N2SdW/nmE1hqakZBSAtQsXkpb6o0/img.png?width=512&amp;amp;height=250&amp;amp;face=0_0_512_250,https://scrap.kakaocdn.net/dn/bS5MWV/hyDkS4EWPd/KVJotgZJcZew3MSlnxMAs1/img.png?width=600&amp;amp;height=300&amp;amp;face=0_0_600_300&quot;&gt;&lt;a href=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&amp;amp;hl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cKyXFQ/hyDk1N2SdW/nmE1hqakZBSAtQsXkpb6o0/img.png?width=512&amp;amp;height=250&amp;amp;face=0_0_512_250,https://scrap.kakaocdn.net/dn/bS5MWV/hyDkS4EWPd/KVJotgZJcZew3MSlnxMAs1/img.png?width=600&amp;amp;height=300&amp;amp;face=0_0_600_300');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;Flutter Code Examples - Google Play 앱&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;Are you a beginner at Flutter? Check out the various features of Flutter through the demo. Source code for all demos is also provided.&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;play.google.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Tutorial/Flutter with App</category>
      <author>독행소년</author>
      <guid isPermaLink="true">https://here4you.tistory.com/256</guid>
      <comments>https://here4you.tistory.com/256#entry256comment</comments>
      <pubDate>Tue, 14 Apr 2020 12:29:00 +0900</pubDate>
    </item>
    <item>
      <title>Flutter Example - TCP Socket Server</title>
      <link>https://here4you.tistory.com/255</link>
      <description>&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1586834394552&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'dart:io';

import 'package:flutter/material.dart';
import 'package:get_ip/get_ip.dart';

SocketServerState pageState;

class SocketServer extends StatefulWidget {
  @override
  SocketServerState createState() {
    pageState = SocketServerState();
    return pageState;
  }
}

class SocketServerState extends State&amp;lt;SocketServer&amp;gt; {
  final scaffoldKey = GlobalKey&amp;lt;ScaffoldState&amp;gt;();

  List&amp;lt;MessageItem&amp;gt; items = List&amp;lt;MessageItem&amp;gt;();

  String localIP = &quot;&quot;;

  ServerSocket serverSocket;
  Socket clientSocket;
  int port = 9000;

  TextEditingController msgCon = TextEditingController();

  @override
  void dispose() {
    stopServer();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    getIP();
    return Scaffold(
      key: scaffoldKey,
      appBar: AppBar(title: Text(&quot;Socket Server&quot;)),
      body: Column(
        children: &amp;lt;Widget&amp;gt;[
          ipInfoArea(),
          messageListArea(),
          submitArea(),
        ],
      ),
    );
  }

  Widget ipInfoArea() {
    return Card(
      child: ListTile(
        dense: true,
        leading: Text(&quot;IP&quot;),
        title: Text(localIP),
        trailing: RaisedButton(
          child: Text((serverSocket == null) ? &quot;Start&quot; : &quot;Stop&quot;),
          onPressed: (serverSocket == null) ? startServer : stopServer,
        ),
      ),
    );
  }

  Widget messageListArea() {
    return Expanded(
      child: ListView.builder(
          reverse: true,
          itemCount: items.length,
          itemBuilder: (context, index) {
            MessageItem item = items[index];
            return Container(
              alignment: (item.owner == localIP)
                  ? Alignment.centerRight
                  : Alignment.centerLeft,
              child: Container(
                margin: const EdgeInsets.symmetric(vertical: 5, horizontal: 10),
                padding:
                    const EdgeInsets.symmetric(vertical: 10, horizontal: 10),
                decoration: BoxDecoration(
                    borderRadius: BorderRadius.circular(10),
                    color: (item.owner == localIP)
                        ? Colors.blue[100]
                        : Colors.grey[200]),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: &amp;lt;Widget&amp;gt;[
                    Text(
                      (item.owner == localIP) ? &quot;Server&quot; : &quot;Client&quot;,
                      style: TextStyle(fontWeight: FontWeight.bold),
                    ),
                    Text(
                      item.content,
                      style: TextStyle(fontSize: 18),
                    ),
                  ],
                ),
              ),
            );
          }),
    );
  }

  Widget submitArea() {
    return Card(
      child: ListTile(
        title: TextField(
          controller: msgCon,
        ),
        trailing: IconButton(
          icon: Icon(Icons.send),
          color: Colors.blue,
          disabledColor: Colors.grey,
          onPressed: (clientSocket != null) ? submitMessage : null,
        ),
      ),
    );
  }

  void getIP() async {
    var ip = await GetIp.ipAddress;
    setState(() {
      localIP = ip;
    });
  }

  void startServer() async {
    print(serverSocket);
    serverSocket =
        await ServerSocket.bind(InternetAddress.anyIPv4, port, shared: true);
    print(serverSocket);
    serverSocket.listen(handleClient);
  }

  void handleClient(Socket client) {
    clientSocket = client;

    showSnackBarWithKey(
        &quot;A new client has connected from ${clientSocket.remoteAddress.address}:${clientSocket.remotePort}&quot;);

    clientSocket.listen(
      (onData) {
        print(String.fromCharCodes(onData).trim());
        setState(() {
          items.insert(
              0,
              MessageItem(clientSocket.remoteAddress.address,
                  String.fromCharCodes(onData).trim()));
        });
      },
      onError: (e) {
        showSnackBarWithKey(e.toString());
        disconnectClient();
      },
      onDone: () {
        showSnackBarWithKey(&quot;Connection has terminated.&quot;);
        disconnectClient();
      },
    );
  }

  void stopServer() {
    disconnectClient();
    serverSocket.close();
    setState(() {
      serverSocket = null;
    });
  }

  void disconnectClient() {
    if (clientSocket != null) {
      clientSocket.close();
      clientSocket.destroy();
    }

    setState(() {
      clientSocket = null;
    });
  }

  void submitMessage() {
    if (msgCon.text.isEmpty) return;
    setState(() {
      items.insert(0, MessageItem(localIP, msgCon.text));
    });
    sendMessage(msgCon.text);
    msgCon.clear();
  }

  void sendMessage(String message) {
    clientSocket.write(&quot;$message\n&quot;);
  }

  showSnackBarWithKey(String message) {
    scaffoldKey.currentState
      ..hideCurrentSnackBar()
      ..showSnackBar(SnackBar(
        content: Text(message),
        action: SnackBarAction(
          label: 'Done',
          onPressed: () {},
        ),
      ));
  }
}

class MessageItem {
  String owner;
  String content;

  MessageItem(this.owner, this.content);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;anigif.gif&quot; data-origin-width=&quot;369&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqXDS5/btqDp1U4jQx/6GfYUpgKMxn3fNCrQMKycK/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqXDS5/btqDp1U4jQx/6GfYUpgKMxn3fNCrQMKycK/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqXDS5/btqDp1U4jQx/6GfYUpgKMxn3fNCrQMKycK/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bqXDS5/btqDp1U4jQx/6GfYUpgKMxn3fNCrQMKycK/img.gif&quot; data-filename=&quot;anigif.gif&quot; data-origin-width=&quot;369&quot; data-origin-height=&quot;800&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://here4you.tistory.com/213&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;▶ Go to Table of Contents | 강의 목차로 이동&lt;/a&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;p style=&quot;font-size: 0.94em;&quot;&gt;&lt;i&gt;※ This example is also available in the &lt;a href=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Flutter Code Examples&lt;/a&gt; app. | 본 예제는 &lt;a href=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Flutter Code Examples&lt;/a&gt; 앱에서도 제공됩니다.&lt;/i&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1571728595725&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Flutter Code Examples - Google Play 앱&quot; data-og-description=&quot;Are you a beginner at Flutter? Check out the various features of Flutter through the demo. Source code for all demos is also provided.&quot; data-og-host=&quot;play.google.com&quot; data-og-source-url=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&quot; data-og-url=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&amp;amp;hl=ko&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cKyXFQ/hyDk1N2SdW/nmE1hqakZBSAtQsXkpb6o0/img.png?width=512&amp;amp;height=250&amp;amp;face=0_0_512_250,https://scrap.kakaocdn.net/dn/bS5MWV/hyDkS4EWPd/KVJotgZJcZew3MSlnxMAs1/img.png?width=600&amp;amp;height=300&amp;amp;face=0_0_600_300&quot;&gt;&lt;a href=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&amp;amp;hl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cKyXFQ/hyDk1N2SdW/nmE1hqakZBSAtQsXkpb6o0/img.png?width=512&amp;amp;height=250&amp;amp;face=0_0_512_250,https://scrap.kakaocdn.net/dn/bS5MWV/hyDkS4EWPd/KVJotgZJcZew3MSlnxMAs1/img.png?width=600&amp;amp;height=300&amp;amp;face=0_0_600_300');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;Flutter Code Examples - Google Play 앱&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;Are you a beginner at Flutter? Check out the various features of Flutter through the demo. Source code for all demos is also provided.&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;play.google.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;</description>
      <category>Tutorial/Flutter with App</category>
      <author>독행소년</author>
      <guid isPermaLink="true">https://here4you.tistory.com/255</guid>
      <comments>https://here4you.tistory.com/255#entry255comment</comments>
      <pubDate>Tue, 14 Apr 2020 12:22:30 +0900</pubDate>
    </item>
    <item>
      <title>Lightsail 사용법 - tail을 이용해서 실시간 로그를 확인하자 | How to use tail</title>
      <link>https://here4you.tistory.com/254</link>
      <description>&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;지난 강좌에서는 forever 툴을 이용해서 Node.js 어플리케이션의 동작을 관리하는 방법에 대해서 알아봤다. 이번 시간에는 tail 명령어를 이용해서 forever가 생성하는 로그파일의 로그내용을 실시간으로 확인하는 방법에 대해서 알아본다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;tail은 forever의 로그파일 뿐만 아니라 모든 종류의 로그파일에 기록되는 실시간 로그를 확인할 수 있는 툴이다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;1. 파일의 로그 확인&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;로그파일의 내용을 확인하는 기본 방법은 다음과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1586414509737&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$&amp;gt; tail hello.log&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;해당 파일의 가장 최근 로그 10라인을 출력해 준다.&lt;/p&gt;
&lt;pre id=&quot;code_1586414541255&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ubuntu@ip-172-26-10-140:~/.forever$ tail hello.log
Thu Apr 09 2020 06:40:58 GMT+0000 (UTC)

:: new request::
Thu Apr 09 2020 06:40:58 GMT+0000 (UTC)

:: new request::
Thu Apr 09 2020 06:40:58 GMT+0000 (UTC)

:: new request::
Thu Apr 09 2020 06:40:58 GMT+0000 (UTC)
ubuntu@ip-172-26-10-140:~/.forever$&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;-n 옵션을 이용하면 원하는 라인의 length를 결정할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1586414617181&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ubuntu@ip-172-26-10-140:~/.forever$ tail -20 hello.log
:: new request::
Thu Apr 09 2020 06:40:58 GMT+0000 (UTC)

:: new request::
Thu Apr 09 2020 06:40:58 GMT+0000 (UTC)

:: new request::
Thu Apr 09 2020 06:40:58 GMT+0000 (UTC)

:: new request::
Thu Apr 09 2020 06:40:58 GMT+0000 (UTC)

:: new request::
Thu Apr 09 2020 06:40:58 GMT+0000 (UTC)

:: new request::
Thu Apr 09 2020 06:40:58 GMT+0000 (UTC)

:: new request::
Thu Apr 09 2020 06:40:58 GMT+0000 (UTC)
ubuntu@ip-172-26-10-140:~/.forever$&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;위 방법들은 로그파일에서 최근 로그내용을 출력하고 다시 프로포트로 돌아온다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;2. 로그파일 추적&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;로그파일에 추가되는 새로운 로그 파일을 지속적으로 추적하기 위해서는 -f 옵션을 이용한다.&lt;/p&gt;
&lt;pre id=&quot;code_1586414727065&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ubuntu@ip-172-26-10-140:~/.forever$ tail -f hello.log
Thu Apr 09 2020 06:40:58 GMT+0000 (UTC)

:: new request::
Thu Apr 09 2020 06:40:58 GMT+0000 (UTC)

:: new request::
Thu Apr 09 2020 06:40:58 GMT+0000 (UTC)

:: new request::
Thu Apr 09 2020 06:40:58 GMT+0000 (UTC)
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;최근 10라인의 로그가 출력되는 것이 첫번째 예제와 유사하지만 프롬포트로 돌아오지 않고 대기하고 있는 것을 확이할 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;이 상태에서 인스턴스에 접속을 해보면...&lt;/p&gt;
&lt;pre id=&quot;code_1586415835552&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ubuntu@ip-172-26-10-140:~/.forever$ tail -f hello.log
Thu Apr 09 2020 06:40:58 GMT+0000 (UTC)

:: new request::
Thu Apr 09 2020 06:40:58 GMT+0000 (UTC)

:: new request::
Thu Apr 09 2020 06:40:58 GMT+0000 (UTC)

:: new request::
Thu Apr 09 2020 06:40:58 GMT+0000 (UTC)

:: new request::
Thu Apr 09 2020 06:52:42 GMT+0000 (UTC)

:: new request::
Thu Apr 09 2020 06:52:42 GMT+0000 (UTC)

:: new request::
Thu Apr 09 2020 07:03:36 GMT+0000 (UTC)

:: new request::
Thu Apr 09 2020 07:03:36 GMT+0000 (UTC)

:: new request::
Thu Apr 09 2020 07:03:37 GMT+0000 (UTC)

:: new request::
Thu Apr 09 2020 07:03:37 GMT+0000 (UTC)

:: new request::
Thu Apr 09 2020 07:03:38 GMT+0000 (UTC)

:: new request::
Thu Apr 09 2020 07:03:38 GMT+0000 (UTC)
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;실시간으로 로그가 추가로 출력되는 것을 확인 할 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;-n 옵션을 추가하면 원하는 라인 length만큼 최근 로그를 출력한 후 새로운 로그를 추적한다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;이번 강좌에서는 tail 명령어를 이용해서 로그파일의 내용을 확인하고 새로운 로그를 추적하는 방법에 대해서 살펴봤다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Development/Cloud computing</category>
      <author>독행소년</author>
      <guid isPermaLink="true">https://here4you.tistory.com/254</guid>
      <comments>https://here4you.tistory.com/254#entry254comment</comments>
      <pubDate>Thu, 9 Apr 2020 16:07:01 +0900</pubDate>
    </item>
    <item>
      <title>Lightsail 사용법 - npm forever 사용법 | How to use npm forever</title>
      <link>https://here4you.tistory.com/253</link>
      <description>&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;지난 강좌에서는 nodemon을 이용해서 실시간으로 디버깅과 테스트를 수행하는 방법에 대해서 알아보았다. nodemon으로 Node.js 어플리케이션을 실행하면 해당 디렉토리의 파일의 변화를 자동 감지해서 어플리케이션이 자동 재실행되게 된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이번 강좌에서는 Node.js 어플리케이션을 백그라운드 데몬으로 실행하는 방법에 대해서 알아본다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;1. forever 설치&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;설치한 우분투 인스턴스로 SSH 접속을 한 후 npm 명령어를 이용해서 golbal하게 forever를 설치한다.&lt;/p&gt;
&lt;pre id=&quot;code_1586405166119&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$&amp;gt; sudo npm install forever -g&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;2. forever start&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;지난 강좌에서 개발했던 helloworld 디렉토리로 이동한 후, forever를 이용해서 Node.js 어플리케이션을 백그라운드 데몬으로 실행해 보자.&lt;/p&gt;
&lt;pre id=&quot;code_1586405518427&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$&amp;gt; sudo forever start index.js&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;forever로 Node.js 어플리케이션을 실행시키는 위해서는 forever start 명령어를 이용한다.&lt;/p&gt;
&lt;p&gt;sudo를 이용해서 forever를 실행하는 이유는 helloworld 어플리케이션에서 사용하는 80번 포트가 root 계정에서만 접근이 가능하기 때문이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;실행화면은 다음과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1586405529979&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ubuntu@ip-172-26-10-140:~/helloworld$ sudo forever start index.js
warn:    --minUptime not set. Defaulting to: 1000ms
warn:    --spinSleepTime not set. Your script will exit if it does not stay up for at least 1000ms
info:    Forever processing file: index.js
ubuntu@ip-172-26-10-140:~/helloworld$&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그럼 웹브라우저를 통해 인스턴스에 접속해보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;이미지 1.png&quot; data-origin-width=&quot;681&quot; data-origin-height=&quot;332&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4yxHO/btqDjGiXJW3/KDSF532HRZKWf3gaZHJt3K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4yxHO/btqDjGiXJW3/KDSF532HRZKWf3gaZHJt3K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4yxHO/btqDjGiXJW3/KDSF532HRZKWf3gaZHJt3K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4yxHO%2FbtqDjGiXJW3%2FKDSF532HRZKWf3gaZHJt3K%2Fimg.png&quot; data-filename=&quot;이미지 1.png&quot; data-origin-width=&quot;681&quot; data-origin-height=&quot;332&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;잠깐 위의 forever 실행화면을 살펴보자. forever로 helloworld 어플리케이션이 실행된 후 다시 프로포트가 나타난다. 이는 forever가 어플리케이션을 백그라운드 데몬으로 실행했기 때문이다. 이 순간부터는 SSH 접속을 종료해도 어플리케이션이 종료되지 않고 지속적으로 서비스를 제공한다. 서버 다운 서비스를 제공한다는 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;3. forever list&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;forever list를 이용하면 forever를 통해 실행되어 동작 중인 백그라운드 데몬의 정보를 확인할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1586405989318&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$&amp;gt; sudo forever list&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;실행화면은 다음과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1586406009789&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ubuntu@ip-172-26-10-140:~/helloworld$ sudo forever list
info:    Forever processes running
data:        uid  command             script   forever pid  id logfile                        uptime
data:    [0] BUFc /usr/local/bin/node index.js 1456    1462    /home/ubuntu/.forever/BUFc.log 0:0:0:29.058
ubuntu@ip-172-26-10-140:~/helloworld$&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;현재 1개의 forever 프로세스(백그라운드 데몬)이 실행 중이며, 식별번호는 0번, uid는 BUFc, 명령어는 nodemon, 실행중인 script는 index.js라는 것을 확인할 수 있다. 추가로 logfile의 위치와 uptime(경과시간)도 확인이 가능하다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;우선 한가지 확인하고 넘어가자. 위 실행을 위해 sudo 명령어를 이용했는데, forever는 sudo 명령어를 사용할 때와 하지 않을 때를 구분한다. 즉 sudo를 이용하지 않고 forever list를 명령하면 다음과 같이 root 권한이 아닌 일반 사용자 권한으로 실행된 프로세스의 정보만 반환한다.(실행한 적이 없으므로 동작중인 프로세스도 없다.)&lt;/p&gt;
&lt;pre id=&quot;code_1586406260846&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ubuntu@ip-172-26-10-140:~/helloworld$ forever list
info:    No forever processes running
ubuntu@ip-172-26-10-140:~/helloworld$&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;4. forever stop&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;forever stop을 이용하면 forever로 실행한 프로세스를 종료할 수 있다. 서버를 정지시키는 기능이다. 다음과 같이 종료할 프로세스의 식별 번호를 부여한다.&lt;/p&gt;
&lt;pre id=&quot;code_1586406398474&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$&amp;gt; sudo forever stop 0&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;실행화면은 다음과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1586406439680&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ubuntu@ip-172-26-10-140:~/helloworld$ sudo forever stop 0
info:    Forever stopped process:
    uid  command             script   forever pid  id logfile                        uptime
[0] BUFc /usr/local/bin/node index.js 1456    1462    /home/ubuntu/.forever/BUFc.log 0:0:1:32.92400000000001
ubuntu@ip-172-26-10-140:~/helloworld$ sudo forever list
info:    No forever processes running
ubuntu@ip-172-26-10-140:~/helloworld$&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;종료 후 다시 list를 조회하니 실행 중인 프로세스가 없음이 확인되었다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;자 그럼 실제 hellowrold 어플리케이션이 종료되었는지 웹브라우저를 통해 인스턴스에 접속해보자. 서버가 종료되어 사이트에 연결할 수 없음이라는 메시지가 출력되고 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;이미지 3.png&quot; data-origin-width=&quot;802&quot; data-origin-height=&quot;416&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cjHORD/btqDkAo9fIJ/snRewcOagIKyX0wsvL4sa0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cjHORD/btqDkAo9fIJ/snRewcOagIKyX0wsvL4sa0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cjHORD/btqDkAo9fIJ/snRewcOagIKyX0wsvL4sa0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcjHORD%2FbtqDkAo9fIJ%2FsnRewcOagIKyX0wsvL4sa0%2Fimg.png&quot; data-filename=&quot;이미지 3.png&quot; data-origin-width=&quot;802&quot; data-origin-height=&quot;416&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;5. forever를 이용한 실시간 디버깅과 테스트&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;지난 강좌에서 nodemon을 이용해서 소스크도를 실시간 디버깅하면서 웹브라우저를 통해 테스트도 진행했었다. forever를 이용해서 어플리케이션을 백그라운드 데몬으로 동작시키면서 실시간 디버깅과 테스트를 하려면 forever start 단계에서 -w 옵션을 추가해야 한다. -w 옵션은 파일의 변화를 감지해서 어플리케이션을 재실행하여 nodemon과 동일한 효과를 제공한다.&lt;/p&gt;
&lt;pre id=&quot;code_1586410225217&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$&amp;gt; sudo forever start -w index.js&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Cloud9을 실행해서 소스코드를 수정해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1586410490296&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var http = require('http');  

http.createServer(function (req, res) {
	res.writeHead(200, {'Content-Type': 'text/plain'});
	res.end('Hello World! Happy World!\nMy World!'); // add Happy World! and My World!
}).listen(80);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;파일을 저장한 후 인스턴스에 다시 접속해보면, 수정된 내용이 실시간으로 반영되는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;이미지 5.png&quot; data-origin-width=&quot;802&quot; data-origin-height=&quot;416&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cEjuIa/btqDjFYPPrp/U03BVnLV3Ts6gaKjj7gOCk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cEjuIa/btqDjFYPPrp/U03BVnLV3Ts6gaKjj7gOCk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cEjuIa/btqDjFYPPrp/U03BVnLV3Ts6gaKjj7gOCk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcEjuIa%2FbtqDjFYPPrp%2FU03BVnLV3Ts6gaKjj7gOCk%2Fimg.png&quot; data-filename=&quot;이미지 5.png&quot; data-origin-width=&quot;802&quot; data-origin-height=&quot;416&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;6. forever 프로세스에 이름부여&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;어플리케이션을 forever로 start할 때 마다 임의의 이름(uid)가 부여된다. 프로세스에 이름을 부여하고자 할 땐 --uid 옵션을 이용한다.&lt;/p&gt;
&lt;pre id=&quot;code_1586410798038&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$&amp;gt; sudo forever start --uid hello -w index.js
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;실행화면은 다음과 같다. uid 항목에 hello라고 명명한 이름이 출력되고 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1586410827390&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ubuntu@ip-172-26-10-140:~/helloworld$ sudo forever start --uid hello -w index.js
warn:    --minUptime not set. Defaulting to: 1000ms
warn:    --spinSleepTime not set. Your script will exit if it does not stay up for at least 1000ms
info:    Forever processing file: index.js
ubuntu@ip-172-26-10-140:~/helloworld$ sudo forever list
info:    Forever processes running
data:        uid   command             script   forever pid  id logfile                         uptime
data:    [0] hello /usr/local/bin/node index.js 1639    1649    /home/ubuntu/.forever/hello.log 0:0:0:8.042
ubuntu@ip-172-26-10-140:~/helloworld$&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;프로세스를 종료할 때 식별번호 외에도 프로세스의 uid도 이용할 수도 있다. hello라고 명명했음으로 다음과 같이 프로세스를 종료할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1586410954074&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ubuntu@ip-172-26-10-140:~/helloworld$ sudo forever stop hello
info:    Forever stopped process:
    uid   command             script   forever pid  id logfile                         uptime
[0] hello /usr/local/bin/node index.js 1639    1649    /home/ubuntu/.forever/hello.log 0:0:1:48.215
ubuntu@ip-172-26-10-140:~/helloworld$ sudo forever list
info:    No forever processes running
ubuntu@ip-172-26-10-140:~/helloworld$&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;실제 서버를 개발할 때 여러개의 forever 프로세스들이 동작하게 되는 경우가 발생한다. 식별번호는 생성 순서로 부여되고 uid도 랜덤하게 부여되어 종료하고자 하는 프로세스를 확인하기 불편한 경우가 발생하는데 이렇게 프로세스에 이름을 부여하면 혼란을 방지할 수 있다. 또한 로그파일을 관리할 때에도 유용하다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;7. forever 로그 관리&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;forever list나 stop를 실행하면 logfile항목에서 해당 프로세스의 로그파일의 경로를 확인할 수 있다. forever로 실행되는 모든 프로세스들은 사용자영역의 .forever 디렉토리에 저장된다. 숨김폴더로 되어 있으므로 ls -all을 해야 보인다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;우선 --uid 옵션으로 hello라고 명칭하고 다시 forever start를 시도하면 다음과 같은 에러가 발생한다.&lt;/p&gt;
&lt;pre id=&quot;code_1586412076994&quot; class=&quot;java&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 15px; color: #383a42; background: #f6f7f8; font-size: 14px; border-radius: 3px; font-family: Menlo, Consolas, Monaco, monospace; border: 1px solid #dddddd; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ubuntu@ip-172-26-10-140:~/helloworld$ sudo forever start --uid hello -w index.js
warn:    --minUptime not set. Defaulting to: 1000ms
warn:    --spinSleepTime not set. Your script will exit if it does not stay up for at least 1000ms
info:    Forever processing file: index.js
error:   Cannot start forever
error:   log file /home/ubuntu/.forever/hello.log exists. Use the -a or --append option to append log.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이는 이미 같은 이름의 로그파일이 .forever 디렉토리에 존재하기 때문이며 -a 나 --append 옵션을 추가해서 기존 로그파일에 로그를 이어서 저장하도록 하거나 기존의 hello.log 파일을 삭제한 후 시도해야 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1586412217237&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$&amp;gt; sudo forever start -a --uid hello -w index.js&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;소스코드를 다음과 같이 수정한 후 저장하자.&lt;/p&gt;
&lt;pre id=&quot;code_1586412269410&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var http = require('http');  

console.log(&quot;::: start HelloWorld Application :::&quot;);
console.log(Date().toString());

http.createServer(function (req, res) {
	console.log(&quot;\n:: new request::&quot;)
	console.log(Date().toString());
	res.writeHead(200, {'Content-Type': 'text/plain'});
	res.end('Hello World! Happy World!\nMy World!'); // add Happy World! and My World!
	
}).listen(80);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;helloworld 어플리케이션이 실행될 때 로그를 한번 출력하고, 인스턴스 접속이 요청될 때마다 로그를 출력하도록 수정한 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;파일을 저장하고 인스턴스에 몇번 접속해 본 후 로그파일을 확인해 보자.&lt;/p&gt;
&lt;pre id=&quot;code_1586413449207&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ubuntu@ip-172-26-10-140:~/.forever$ cat hello.log
error: Could not read .foreverignore file.
error: ENOENT: no such file or directory, open '/home/ubuntu/helloworld/.foreverignore'
::: start HelloWorld Application :::
Thu Apr 09 2020 05:57:18 GMT+0000 (UTC)

:: new request::
Thu Apr 09 2020 05:57:32 GMT+0000 (UTC)

:: new request::
Thu Apr 09 2020 05:57:32 GMT+0000 (UTC)
error: Forever detected script was killed by signal: SIGKILL
error: Could not read .foreverignore file.
error: ENOENT: no such file or directory, open '/home/ubuntu/helloworld/.foreverignore'
::: start HelloWorld Application :::
Thu Apr 09 2020 06:03:44 GMT+0000 (UTC)

:: new request::
Thu Apr 09 2020 06:04:37 GMT+0000 (UTC)

:: new request::
Thu Apr 09 2020 06:04:37 GMT+0000 (UTC)

:: new request::
Thu Apr 09 2020 06:22:47 GMT+0000 (UTC)

:: new request::
Thu Apr 09 2020 06:22:48 GMT+0000 (UTC)

:: new request::
Thu Apr 09 2020 06:22:48 GMT+0000 (UTC)

:: new request::
Thu Apr 09 2020 06:22:48 GMT+0000 (UTC)
ubuntu@ip-172-26-10-140:~/.forever$&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;.forever 디렉토리의 모든 로그파일을 삭제하고 할 때에는 cleanlogs 명령을 이용하면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1586413580252&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ubuntu@ip-172-26-10-140:~/.forever$ ls
2oEA.log  6kJY.log  EMP-.log  L2rx.log  T-0N.log  config.json  oNUE.log  r6tR.log  z8Co.log
2pf-.log  78ME.log  FYH6.log  P5b9.log  WgGt.log  hVb7.log     pb-3.log  sock      z8IB.log
5SoP.log  BUFc.log  HW.log    QP6i.log  YAEA.log  hello.log    pids      xRRe.log
5SsO.log  D70J.log  IAPx.log  SoGg.log  bwKh.log  mv0N.log     r1ZQ.log  xf4O.log
ubuntu@ip-172-26-10-140:~/.forever$ forever cleanlogs
ubuntu@ip-172-26-10-140:~/.forever$ ls
config.json  pids  sock
ubuntu@ip-172-26-10-140:~/.forever$&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;자 정리할 시간다.&lt;/p&gt;
&lt;p&gt;forever를 이용하면 Node.js 어플리케이션을 백그라운드 데몬으로 안정적으로 실행/종료시킬 수 있다.&lt;/p&gt;
&lt;p&gt;-w 옵션을 이용하면, nodemon을 이용했을 때 처럼 실시간으로 디버깅과 테스트를 진행할 수 있다.&lt;/p&gt;
&lt;p&gt;-uid 옵션을 이용하면, 프로세스에 이름을 부여할 수 있으며, 이 이름으로 로그파일이 저장된다.&lt;/p&gt;
&lt;p&gt;-a 옵션을 이용하면 기존의 로그파일에 이어서 로그가 추가된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;추가로 지난 강좌에서 설치했던 Cloud9을 forever로 실행하는 예제는 다음과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1590309838876&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$&amp;gt; forever start -a --uid cloud9 server.js -w /home/ubuntu/helloworld/ -l 0.0.0.0 -p 9002 -a test:test1234&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;다은 시간에는 실시간으로 로그파일을 확인하는 방법에 대해서 알아본다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Development/Cloud computing</category>
      <author>독행소년</author>
      <guid isPermaLink="true">https://here4you.tistory.com/253</guid>
      <comments>https://here4you.tistory.com/253#entry253comment</comments>
      <pubDate>Thu, 9 Apr 2020 15:35:21 +0900</pubDate>
    </item>
    <item>
      <title>Lightsail 사용법 - nodemon 사용법 | How to use nodemon</title>
      <link>https://here4you.tistory.com/252</link>
      <description>&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;이번 강좌에서는 nodemon을 이용해서 Node.js 기반의 프로젝트를 실시간으로 수정하면서 테스트까지 수행하는 방법에 대해서 알아본다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;nodemon은 파일에 변경이 발생하면 node 어플리케이션을 자동으로 재실행해주는 프로그램이다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;b&gt;1. nodemon 설치&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;npm 명령어를 이용해서 global하게 nodemon을 설치한다.&lt;/p&gt;
&lt;pre id=&quot;code_1586274115464&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$&amp;gt; sudo npm install nodemon -g&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;b&gt;2. nodemon을 이용해서 node 어플리케이션 실행&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;지난 강좌에서 작업했던 helloworld 프로젝트로 이동한 후&amp;nbsp;nodemon 명령어를 이용해서 helloworld 프로젝트를 실행한다.&lt;/p&gt;
&lt;pre id=&quot;code_1586274304106&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$&amp;gt; sudo nodemon index.js&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;다음과 같이 출력되면 정상 동작하는 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;이미지 4.png&quot; data-origin-width=&quot;754&quot; data-origin-height=&quot;200&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b0vO6P/btqDf1H4xGb/BZTAa9jKcKjFvw2oA4ZIg1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b0vO6P/btqDf1H4xGb/BZTAa9jKcKjFvw2oA4ZIg1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b0vO6P/btqDf1H4xGb/BZTAa9jKcKjFvw2oA4ZIg1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb0vO6P%2FbtqDf1H4xGb%2FBZTAa9jKcKjFvw2oA4ZIg1%2Fimg.png&quot; data-filename=&quot;이미지 4.png&quot; data-origin-width=&quot;754&quot; data-origin-height=&quot;200&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;웹브라우저틀 통해 인스턴스로 접속해보자. 정상 동작중임을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;이미지 5.png&quot; data-origin-width=&quot;687&quot; data-origin-height=&quot;290&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/beriuw/btqDiFqhBcY/tjcjMA7maKamCAM8nwNo00/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/beriuw/btqDiFqhBcY/tjcjMA7maKamCAM8nwNo00/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/beriuw/btqDiFqhBcY/tjcjMA7maKamCAM8nwNo00/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fberiuw%2FbtqDiFqhBcY%2FtjcjMA7maKamCAM8nwNo00%2Fimg.png&quot; data-filename=&quot;이미지 5.png&quot; data-origin-width=&quot;687&quot; data-origin-height=&quot;290&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;이제 소스코드(index.js)를 편집할 차례이다. 지난 강좌에서 설치한 Cloud9을 실행해서 접속하자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;이미지 6.png&quot; data-origin-width=&quot;937&quot; data-origin-height=&quot;576&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dkO6Wr/btqDiFjvPxJ/OGKiumrY2lO6nZK598uvw1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dkO6Wr/btqDiFjvPxJ/OGKiumrY2lO6nZK598uvw1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dkO6Wr/btqDiFjvPxJ/OGKiumrY2lO6nZK598uvw1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdkO6Wr%2FbtqDiFjvPxJ%2FOGKiumrY2lO6nZK598uvw1%2Fimg.png&quot; data-filename=&quot;이미지 6.png&quot; data-origin-width=&quot;937&quot; data-origin-height=&quot;576&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;index.js 파일을 다음과 같이 수정한다.&lt;/p&gt;
&lt;pre id=&quot;code_1586274672167&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var http = require('http');  

http.createServer(function (req, res) {
	res.writeHead(200, {'Content-Type': 'text/plain'});
	res.end('Hello World! Happy World!'); // add Happy World!
}).listen(80);&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;이제 Ctrl+S를 눌러 파일을 저장해보자. 이 때 nodemon을 실행한 콘솔의 로그변화에 주목하자. index.js 파일의 변화를 감지하고 재시작하고 있음을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;이미지 7.png&quot; data-origin-width=&quot;754&quot; data-origin-height=&quot;216&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/N0Of6/btqDf1858ie/2BJRahrgTPVmqkiKKKYIj1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/N0Of6/btqDf1858ie/2BJRahrgTPVmqkiKKKYIj1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/N0Of6/btqDf1858ie/2BJRahrgTPVmqkiKKKYIj1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FN0Of6%2FbtqDf1858ie%2F2BJRahrgTPVmqkiKKKYIj1%2Fimg.png&quot; data-filename=&quot;이미지 7.png&quot; data-origin-width=&quot;754&quot; data-origin-height=&quot;216&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;Hello World!를 출력하던 브라우저도 새로고침해 보자. 문구가 변경된 것을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;이미지 9.png&quot; data-origin-width=&quot;687&quot; data-origin-height=&quot;290&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Wz2Ve/btqDeUJqpsE/6KtADCzmfOApbsZWD5bF01/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Wz2Ve/btqDeUJqpsE/6KtADCzmfOApbsZWD5bF01/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Wz2Ve/btqDeUJqpsE/6KtADCzmfOApbsZWD5bF01/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWz2Ve%2FbtqDeUJqpsE%2F6KtADCzmfOApbsZWD5bF01%2Fimg.png&quot; data-filename=&quot;이미지 9.png&quot; data-origin-width=&quot;687&quot; data-origin-height=&quot;290&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;이제부터는 소스코드를 변경하고 저장할 때 마다 자동으로 어플리케이션이 재실행되면서 변경된 내용을 바로바로 테스트하고 확인할 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;기존의 JSP나 Spring 프로젝트의 개발과 잠깐 비표해 보면 어떨까? 아.. 아니 비교하지 말자. 아마 경험자라면 얼마나 생산성이 향상되는 것인지 알 것이다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;그런데 이번 강좌에서 사용한 nodemon은 기본적으로 foregound에서 동작한다. 터미널을 종료하기 위해서는 nodemon이 먼저 종료되어야 한다. 그럼 실제 서비스 운영에서는 어떻게해야 할까? 당연히 backgound 데몬으로 어플리케이션을 동작시켜야 한다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;다음시간에는 forever 툴을 이용해서 Node.js 어플리케이션을 backgound 데몬으로 실행하고 관리하는 방법에 대해서 알아본다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Development/Cloud computing</category>
      <author>독행소년</author>
      <guid isPermaLink="true">https://here4you.tistory.com/252</guid>
      <comments>https://here4you.tistory.com/252#entry252comment</comments>
      <pubDate>Wed, 8 Apr 2020 01:03:12 +0900</pubDate>
    </item>
    <item>
      <title>Lightsail 사용법 - Node.js 개발환경 구축 | Cloud9 설치 방법 | How to install Cloud9 locally</title>
      <link>https://here4you.tistory.com/251</link>
      <description>&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;이번 시간에는 Node.js 개발환경을 구축하는 방법에 대해서 알아본다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;기존에 웹 서비스를 개발할 때에는 로컬 머신에 개발 환경을 구축한 후 구현한 소스코드 혹은 바이너리를 서버로 업로드 한 후 테스트하는 방식이 주로 사용되었다. 번거롭고 귀찮고 시간도 많이 드는 작업이었다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;가장 심플한 방법은 개발과 테스트를 한 머신에서 하는 것이다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;이번 강좌에서는 Cloud9에 대해서 소개한다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;Cloud9은 오프 소스 기반의 온라인 통합 개발 환경으로 쉽게 말해 웹브라우저에서 동작하는 IDE 툴이다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;현재 AWS에서 Cloud9을 서비스하고 있지만 이와 별개로 자신의 머신에 직접 설치해서 사용할 수 도 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;b&gt;1. Cloud9 다운로드&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;Cloud9은 git을 통해 배포되고 있으며 Windows, Linux 등에서 사용 가능하다. git을 이용해 Cloud9을 다운로드 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1586241939173&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ubuntu@ip-172-26-10-140:~$ git clone https://github.com/c9/core.git cloud9
Cloning into 'cloud9'...
remote: Enumerating objects: 52830, done.
remote: Total 52830 (delta 0), reused 0 (delta 0), pack-reused 52830
Receiving objects: 100% (52830/52830), 35.34 MiB | 10.02 MiB/s, done.
Resolving deltas: 100% (32446/32446), done.
ubuntu@ip-172-26-10-140:~$ ls
cloud9  helloworld
ubuntu@ip-172-26-10-140:~$&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;cloud9 디렉토리로 소스코드가 다운로드되었다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;b&gt;2. Cloud9 설치&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;cloud9/script 디렉토리로 이동해서 cloud9을 설치 스크립트를 실행한다.&lt;/p&gt;
&lt;pre id=&quot;code_1586242124520&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$&amp;gt; ./install-sdk.sh&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;Success! 문구가 출력되면 설치에 성공한 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;이미지 13.png&quot; data-origin-width=&quot;914&quot; data-origin-height=&quot;600&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Ya8v7/btqDg5XcXCZ/PVYZG3pugk9kDooWxkOiAK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Ya8v7/btqDg5XcXCZ/PVYZG3pugk9kDooWxkOiAK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Ya8v7/btqDg5XcXCZ/PVYZG3pugk9kDooWxkOiAK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYa8v7%2FbtqDg5XcXCZ%2FPVYZG3pugk9kDooWxkOiAK%2Fimg.png&quot; data-filename=&quot;이미지 13.png&quot; data-origin-width=&quot;914&quot; data-origin-height=&quot;600&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;b&gt;3. Cloud9용 포트 개방&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;Cloud9이 사용할 포트를 개방해주자. Lightsail에 접속하여 방화벽 설정 페이지로 이동한다. Cloud9용으로 9002번을 할당해 주면 다음과 같이 사용자용 포트로 적용된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;이미지 14.png&quot; data-origin-width=&quot;1121&quot; data-origin-height=&quot;491&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cyawsE/btqDeJA0qU2/4IMUJMHOFSZbLdnyDgnNY1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cyawsE/btqDeJA0qU2/4IMUJMHOFSZbLdnyDgnNY1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cyawsE/btqDeJA0qU2/4IMUJMHOFSZbLdnyDgnNY1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcyawsE%2FbtqDeJA0qU2%2F4IMUJMHOFSZbLdnyDgnNY1%2Fimg.png&quot; data-filename=&quot;이미지 14.png&quot; data-origin-width=&quot;1121&quot; data-origin-height=&quot;491&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;b&gt;4. Cloud9 실행&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;cloud9 디렉토리로 이동한 후 Cloud9을 실행한다.&lt;/p&gt;
&lt;pre id=&quot;code_1586242627951&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;node server.js -w /home/ubuntu/helloworld/ -l 0.0.0.0 -p 9002 -a test:test1234&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;명령어를 분석하면 다음과 같다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;node server.js: server.js 파일을 Node.js 엔진으로 실행하다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;-w /home/ubuntu/helloworld/: 워크스페이스(개발할 프로젝트)를 설정한다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;-l 0.0.0.0: 접속허용할 클라이언트 IP를 설정&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;-p 9002: 접속에 사용할 포트&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;-a test:test1234: 접속에 사용할 ID와 비밀번호&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;이미지 15.png&quot; data-origin-width=&quot;938&quot; data-origin-height=&quot;232&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bSTXYg/btqDc3UtnQX/JMrEsoK7CY1mMzOMM16ekK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bSTXYg/btqDc3UtnQX/JMrEsoK7CY1mMzOMM16ekK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bSTXYg/btqDc3UtnQX/JMrEsoK7CY1mMzOMM16ekK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbSTXYg%2FbtqDc3UtnQX%2FJMrEsoK7CY1mMzOMM16ekK%2Fimg.png&quot; data-filename=&quot;이미지 15.png&quot; data-origin-width=&quot;938&quot; data-origin-height=&quot;232&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;b&gt;5. Cloud9 접속&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;웹브라우저를 통해 Cloud9으로 접속한다. 로그인 창이 나타면 ID와 비밀번호를 입력한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;이미지 17.png&quot; data-origin-width=&quot;768&quot; data-origin-height=&quot;491&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d5hE0B/btqDf09XbtD/vIhLRFKpy6tKxSzcB4xdck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d5hE0B/btqDf09XbtD/vIhLRFKpy6tKxSzcB4xdck/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d5hE0B/btqDf09XbtD/vIhLRFKpy6tKxSzcB4xdck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd5hE0B%2FbtqDf09XbtD%2FvIhLRFKpy6tKxSzcB4xdck%2Fimg.png&quot; data-filename=&quot;이미지 17.png&quot; data-origin-width=&quot;768&quot; data-origin-height=&quot;491&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;인증에 성공하면 다음과 같이 IDE가 출력된다. 화면 좌측에는 개발중이던 helloworld 프로젝트(워크스페이스)가 활성화되어 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;이미지 19.png&quot; data-origin-width=&quot;1237&quot; data-origin-height=&quot;930&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9HHJv/btqDc46YI56/QSP4StpL7PX8ejWvGjPKe1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9HHJv/btqDc46YI56/QSP4StpL7PX8ejWvGjPKe1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9HHJv/btqDc46YI56/QSP4StpL7PX8ejWvGjPKe1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9HHJv%2FbtqDc46YI56%2FQSP4StpL7PX8ejWvGjPKe1%2Fimg.png&quot; data-filename=&quot;이미지 19.png&quot; data-origin-width=&quot;1237&quot; data-origin-height=&quot;930&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;워크스페이스에서 index.js 파일을 클릭하면 해당 파일이 열리게 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;이미지 20.png&quot; data-origin-width=&quot;993&quot; data-origin-height=&quot;801&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Pg8q8/btqDecpSpbQ/YD7DthplRkcTbkG19URZ90/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Pg8q8/btqDecpSpbQ/YD7DthplRkcTbkG19URZ90/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Pg8q8/btqDecpSpbQ/YD7DthplRkcTbkG19URZ90/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPg8q8%2FbtqDecpSpbQ%2FYD7DthplRkcTbkG19URZ90%2Fimg.png&quot; data-filename=&quot;이미지 20.png&quot; data-origin-width=&quot;993&quot; data-origin-height=&quot;801&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;화면 하단에는 터미널도 제공한다. index.js 파일을 실행해보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;이미지 21.png&quot; data-origin-width=&quot;993&quot; data-origin-height=&quot;758&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MlJyA/btqDgzjVp06/pIxW5OVOCvJLzvQG0ACmCk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MlJyA/btqDgzjVp06/pIxW5OVOCvJLzvQG0ACmCk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MlJyA/btqDgzjVp06/pIxW5OVOCvJLzvQG0ACmCk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMlJyA%2FbtqDgzjVp06%2FpIxW5OVOCvJLzvQG0ACmCk%2Fimg.png&quot; data-filename=&quot;이미지 21.png&quot; data-origin-width=&quot;993&quot; data-origin-height=&quot;758&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;웹브라우저를 통해 인스턴스에 접속해보자. Hello World!가 정상 출력되고 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;이미지 22.png&quot; data-origin-width=&quot;691&quot; data-origin-height=&quot;496&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmjnmW/btqDf2Nq3G4/wVrqwxPpz2EkskXwekKPJ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmjnmW/btqDf2Nq3G4/wVrqwxPpz2EkskXwekKPJ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmjnmW/btqDf2Nq3G4/wVrqwxPpz2EkskXwekKPJ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmjnmW%2FbtqDf2Nq3G4%2FwVrqwxPpz2EkskXwekKPJ0%2Fimg.png&quot; data-filename=&quot;이미지 22.png&quot; data-origin-width=&quot;691&quot; data-origin-height=&quot;496&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;자 정리할 시간이다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;Cloud9은 웹브라우저에서 동작하는 IDE로 어떠한 운영체제에서도 설치/운영이 가능하다. 그래서 별도의 개발머신(로컬머신)을 운영하지 않고 클라우드 상에 직접 설치하여 Node.js 프로젝트를 개발할 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;이제 우리는 언제 어디서 어떠한 머신을 이용하든 웹브라우저만 있으면 프로젝트를 이어서 개발할 수 있게 되었다. 심지어 안드로이드 크롬상에서도 동작한다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;다만 아직 한가지 아쉬운점이 있다. 소스코드를 변경할 때마다 실행중인 node를 재실행해야 변경사항이 반영된다. 다음 강좌에서는 Cloud9상에서 파일을 저장하는 것 만으로 변경사항이 바로 반영되도록 하는 방법에 대해서 소개한다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Development/Cloud computing</category>
      <author>독행소년</author>
      <guid isPermaLink="true">https://here4you.tistory.com/251</guid>
      <comments>https://here4you.tistory.com/251#entry251comment</comments>
      <pubDate>Tue, 7 Apr 2020 16:21:30 +0900</pubDate>
    </item>
    <item>
      <title>Lightsail 사용법 - Node.js 설치 | Node.js 환경 구성</title>
      <link>https://here4you.tistory.com/250</link>
      <description>&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;이번 강좌에서는 Lightsail에 생성한 우분투 인스턴스에 Node.js를 설치하고 테스트 하는 방법에 대해서 알아본다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;사족으로...&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;Node.js는 Javascript의 일종으로, 구글의 Chrome V8 JavaScript 엔진으로 빌드된 Javascript용 런타임이다. 주로 서버 사이드 스크립트로 활용되어 동적인 웹사이트를 구축하는데 사용되는데 가벼우면서도 높은 처리 성능을 특징으로 가진다. 특히 기존의 JSP(Spring) 기반의 프레임워크로 사이트를 구축하는 것 대비 훨씬 높은 생산성을 자랑한다. 개발자가 직접 빌드하는 과정도 생략되기 때문에 실시간으로 소스코드를 수정해 가면서 테스트도 할 수 있다.(이거 정말 획기적이면서도 편리하면서도 파워플하다.)&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;그리고 클라이언트 사이트 스크립트로도 활용가능해서 라즈베리파이와 같은 소형 리눅스머신에서 어플리케이션을 개발하는데도 활용할 수 있다.(언제간 해보고 싶지만 아직 경험은 없다.)&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;b&gt;1. npm 설치&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;npm은 자바스크립트 프로그래밍 언어를 위한 패키지 관리자로 개발환경을 쉽게 설치할 수 있도록 지원하는 매니저 프로그램이다. Node.js를 설치하기 위해서는 npm을 먼저 설치한다.&lt;/p&gt;
&lt;pre id=&quot;code_1586237830207&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$&amp;gt; sudo apt-get install npm&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;b&gt;2. n 설치&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;n은 Node.js 엔진을 설치/관리하는 매니저 프로그램으로 npm을 통해 설치 가능하다.&lt;/p&gt;
&lt;pre id=&quot;code_1586238019033&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$&amp;gt; sudo npm install n -g&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;npm install을 이용해서 각종 패키지를 설치할 수 있는데, 기본적으로 현재 디렉토리에 node_modules라는 서브 디렉토리로 해당 패키지가 설치된다. 그리고 설치된 패키지는 현재 디렉토리에서만 사용이 가능하다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;-g (golbal) 옵션을 이용하면 해당 패키지는 현재 디렉토리가 아닌 /usr/local/ 디렉토리에 설치되며 모든 디렉토리에서 사용이 가능하다. 즉 현재 디렉토리 경로와 무관하에 어디서든 사용가능한 패키지로 설치하고자 할 때 -g 옵션을 사용한다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;b&gt;3. Node.js 설치&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;설치한 n을 이용해서 Node.js를 설치한다.&lt;/p&gt;
&lt;pre id=&quot;code_1586238447318&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$&amp;gt; sudo n 8.12.0&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;2020년 4월 현재 Node.js는 13 버전까지 출시했다. 하지만 향후 Firebase 등과 연동할 필요가 있으므로 Firebase에서 지원하는 8 버전대를 설치한다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;b&gt;4. Node.js 설치 확인&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;설치한 Node.js를 확인하기 위해 n을 실행해본다.&lt;/p&gt;
&lt;pre id=&quot;code_1586238620157&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$&amp;gt; n&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;8.12.0 버전이 설치 된 것을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;이미지 8.png&quot; data-origin-width=&quot;914&quot; data-origin-height=&quot;168&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MTbBV/btqDc372nOz/ofG6kjarcGoObykrm5ZK8k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MTbBV/btqDc372nOz/ofG6kjarcGoObykrm5ZK8k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MTbBV/btqDc372nOz/ofG6kjarcGoObykrm5ZK8k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMTbBV%2FbtqDc372nOz%2FofG6kjarcGoObykrm5ZK8k%2Fimg.png&quot; data-filename=&quot;이미지 8.png&quot; data-origin-width=&quot;914&quot; data-origin-height=&quot;168&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;b&gt;5. Node.js 첫번째 프로젝트&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;이제 간단한 프로젝트를 만들어서 동작을 테스트 해보자.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;우선 helloworld란 디렉토리를 생성한 후 진입한다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;npm 명령어로 프로젝트를 초기화한다.&lt;/p&gt;
&lt;pre id=&quot;code_1586238991889&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$&amp;gt; npm init&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;이미지 10.png&quot; data-origin-width=&quot;914&quot; data-origin-height=&quot;696&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9kOGa/btqDgzc4g4z/bx6ti3K6wYRwFZxcPGFoy1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9kOGa/btqDgzc4g4z/bx6ti3K6wYRwFZxcPGFoy1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9kOGa/btqDgzc4g4z/bx6ti3K6wYRwFZxcPGFoy1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9kOGa%2FbtqDgzc4g4z%2Fbx6ti3K6wYRwFZxcPGFoy1%2Fimg.png&quot; data-filename=&quot;이미지 10.png&quot; data-origin-width=&quot;914&quot; data-origin-height=&quot;696&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;명령어를 입력하면 프로젝트를 초기화하면서 프로젝트명과 버전, 작성자 등등의 값을 요구하는데 필요한 값은 입력하고 불필요한 값은 생략하면서 진행하면 결과적으로 package.json 파일이 생성된다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;package.json 파일의 내용을 살펴보자.&lt;/p&gt;
&lt;pre id=&quot;code_1586239219707&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ubuntu@ip-172-26-10-140:~/helloworld$ cat package.json
{
  &quot;name&quot;: &quot;helloworld&quot;,
  &quot;version&quot;: &quot;1.0.0&quot;,
  &quot;description&quot;: &quot;First Project&quot;,
  &quot;main&quot;: &quot;index.js&quot;,
  &quot;scripts&quot;: {
    &quot;test&quot;: &quot;echo \&quot;Error: no test specified\&quot; &amp;amp;&amp;amp; exit 1&quot;
  },
  &quot;author&quot;: &quot;here4you&quot;,
  &quot;license&quot;: &quot;ISC&quot;
}
ubuntu@ip-172-26-10-140:~/helloworld$&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;프로젝트 초기화단계에서 입력한 값들이 json 포맷으로 정리되어 있다. 유념해서 봐야할 부분은 main 항목이다. 이 프로젝트의 실행 시작점이 index.js 파일이라는 것을 기술한 것이다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;그럼 index.js 파일을 생성해서 첫번째 프로그램을 작성해 보자.&amp;nbsp;예제 소스는 &lt;a href=&quot;https://www.w3schools.com/nodejs/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;w3schools.com&lt;/a&gt;의 내용을 참고했다.&lt;/p&gt;
&lt;pre id=&quot;code_1586240053176&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var http = require('http');

http.createServer(function (req, res) {
        res.writeHead(200, {'Content-Type': 'text/plain'});
        res.end('Hello World!');
}).listen(80);&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;파일을 저장한 후 프로젝트를 실행해 보자. sudo를 이용한 이유는 80번 포트는 root 계정만 사용할 수 있기 때문이다.&lt;/p&gt;
&lt;pre id=&quot;code_1586240132534&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$&amp;gt; sudo node index.js&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;다음과 같이 별다른 내용없이 커서만 깜빡이고 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;이미지 11.png&quot; data-origin-width=&quot;914&quot; data-origin-height=&quot;120&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b4N5Lg/btqDdIvLonw/qv5xfk95ZsEKGKKUgQCjFk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b4N5Lg/btqDdIvLonw/qv5xfk95ZsEKGKKUgQCjFk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b4N5Lg/btqDdIvLonw/qv5xfk95ZsEKGKKUgQCjFk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb4N5Lg%2FbtqDdIvLonw%2Fqv5xfk95ZsEKGKKUgQCjFk%2Fimg.png&quot; data-filename=&quot;이미지 11.png&quot; data-origin-width=&quot;914&quot; data-origin-height=&quot;120&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;웹브라우저를 열어서 인스턴스의 고정 IP를 입력해 보자.(위 그림에 표시되는 IP 주소는 프라이빗 IP 주소이니 혼돈하지 말자)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;이미지 12.png&quot; data-origin-width=&quot;765&quot; data-origin-height=&quot;486&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmS1c7/btqDgyrJgWl/4RMg5Y3KApJ41kROm4HeUK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmS1c7/btqDgyrJgWl/4RMg5Y3KApJ41kROm4HeUK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmS1c7/btqDgyrJgWl/4RMg5Y3KApJ41kROm4HeUK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmS1c7%2FbtqDgyrJgWl%2F4RMg5Y3KApJ41kROm4HeUK%2Fimg.png&quot; data-filename=&quot;이미지 12.png&quot; data-origin-width=&quot;765&quot; data-origin-height=&quot;486&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;Hello World! 라는 문구가 출력되면 정상적으로 동작하는 것이다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;자 지금까지 강좌를 되돌아 보자.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;우린 Lightsail에 회원가입을 해서 월 3.5$짜리(그러나 한달은 무료)인 VM 인스턴스에 우분투를 설치(라고는 하나 그저 클릭 몇 번)하고, 전세계 어디서든 접속이 가능하도록 고정 IP를 할당하였으며, 아주 안전하게 SSH 접속이 가능하도록 환경을 구축하였다. 그리고 Node.js 런타임을 설치하고 정상 동작하는 것을 확인했다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;만약 동일한 작업을 로컬머신에서 한다고 가정해보자. 2기가나 되는 우분투 이미지를 다운로드해야 하고, 하드디스크를 파티셔닝해서 직접 설치하거나 VMware등의 툴을 이용해서 VM으로 설치해야 한다. 시간도 꽤 걸릴 것이다. 만약 개발 도중 환경이 꼬여서 재설치를 해야 한다고 하면 굉장히 골치아파진다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;다시 Lightsail로 돌아가보자. 개발 도중 환경이 꼬이거나 맘에 안들어서 새로운 우분투 머신이 필요하다면 1분이면 충분하다. 이 얼마나 우아한가?&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;그런데 한가지 걸리는게 있다. 개발은 어떻게 해야 하는가? 뭔가 더 우아한 방법이 있는것인가?&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;있다. 그 환상적인 방법을 다음 포스팅에서 다룬다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Development/Cloud computing</category>
      <author>독행소년</author>
      <guid isPermaLink="true">https://here4you.tistory.com/250</guid>
      <comments>https://here4you.tistory.com/250#entry250comment</comments>
      <pubDate>Tue, 7 Apr 2020 15:28:50 +0900</pubDate>
    </item>
    <item>
      <title>Lightsail 사용법 - 방화벽 설정 및 SSH 포트 설정</title>
      <link>https://here4you.tistory.com/249</link>
      <description>&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;지난 강좌에서는 생성한 인스턴스에 고정IP를 부여하고 SSH Key를 이용해서 로컬장비에서 SSH 접속을 하는 방법에 대해서 알아보았다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;이번 시간에는 방화벽에 대해서 알아본다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;일반적인 데비안 계열의 리눅스에서는 방화벽 설정을 위해 UFW(Uncomplicated Firewall)이라는 방화벽 관리 프로그램을 사용한다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;하지만 Lightsail의 경우 자체 방화벽을 제공한다. 설치한 우분투 인스턴스에서 UFW를 사용할 수도 있지만 Lightsail의 방화벽을 이용하는 것이 더 확실하다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;설치한 인스턴스의 관리-&amp;gt;네트워킹 페이지로 이동하면 방화벽 설정이 가능하다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;이미지 1.png&quot; data-origin-width=&quot;1105&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OBnPN/btqDfBa8zSr/qGj6Qxkd7efs97Wkaka9x1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OBnPN/btqDfBa8zSr/qGj6Qxkd7efs97Wkaka9x1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OBnPN/btqDfBa8zSr/qGj6Qxkd7efs97Wkaka9x1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOBnPN%2FbtqDfBa8zSr%2FqGj6Qxkd7efs97Wkaka9x1%2Fimg.png&quot; data-filename=&quot;이미지 1.png&quot; data-origin-width=&quot;1105&quot; data-origin-height=&quot;1080&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;살펴보면, SSH 접속을 위한 TCP 22번 포트와 HTTP 요청을 수신하기 위한 TCP 80번 포트가 오픈되어 있는 것을 확인할 수 있다. 이 곳에서 필요한 포트를 추가 개방하면된다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;b&gt;HTTP 통신을 위한 TCP 80번 포트&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;우리가 웹브라우저를 이용해서 특정 사이트에 접속할 때 사용하는 프로토콜이 HTTP(혹은 HTTPS)이며 사용하는 기본 포트가 80번 이다. 만약 이 인스턴스에서 웹사이트를 구축해서 서비스를 한다면 80번 포트가 개방되어 있어야 사용자들이 브라우저틀 통해 접속할 수 있게 되는 것이다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;b&gt;SSH 접속을 위한 TCP 22번 포트&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;지난 강좌들에서 로컬머신에서 인스턴스로 SSH 접속을 했을 때 사용한 SSH 통신용 기본 포트가 22번 이다.&amp;nbsp; 그렇기 때문에 Lightsail 처럼 자체 방화벽을 제공하지 않는 호스팅 서비스의 경우 22번 포트를 통한 SSH 공격이 무지하게 들어온다. 그래서 습관상 SSH 포트를 변경하곤 한다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;그럼 SSH 포트를 변경하고, 변경된 포트로 SSH 접속하는 실습을 진행해보자.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;우선 새로운 SSH 포트로 사용할 9001포트를 추가한다. 아직 기본 SSH 포트를 삭제하진 않는다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;이미지 3.png&quot; data-origin-width=&quot;1105&quot; data-origin-height=&quot;818&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SMrw9/btqDeUhL7Hd/fp8Kerq8WUb71kU1EDvg9K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SMrw9/btqDeUhL7Hd/fp8Kerq8WUb71kU1EDvg9K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SMrw9/btqDeUhL7Hd/fp8Kerq8WUb71kU1EDvg9K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSMrw9%2FbtqDeUhL7Hd%2Ffp8Kerq8WUb71kU1EDvg9K%2Fimg.png&quot; data-filename=&quot;이미지 3.png&quot; data-origin-width=&quot;1105&quot; data-origin-height=&quot;818&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;로컬머신에서 우분투 인스턴스로 SSH 접속한다.&lt;/p&gt;
&lt;pre id=&quot;code_1586230378231&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$&amp;gt; ssh -i LightsailDefaultKey-ap-northeast-2.pem ubuntu@13.209.31.173&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;vim 에디터를 이용해서 /etc/ssh/sshd_config 파일을 수정한다.&lt;/p&gt;
&lt;pre id=&quot;code_1586230498243&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$&amp;gt; sudo vim /etc/ssh/sshd_config&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;주석처리되어 있는 Port 항목에서 주석을 제거하고 22번이던 기본 값을 9001로 변경한 다음 파일을 저장한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;이미지 4.png&quot; data-origin-width=&quot;914&quot; data-origin-height=&quot;552&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YR8g6/btqDfB95on0/s9FCkkL3viF2ZHu7D5uTiK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YR8g6/btqDfB95on0/s9FCkkL3viF2ZHu7D5uTiK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YR8g6/btqDfB95on0/s9FCkkL3viF2ZHu7D5uTiK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYR8g6%2FbtqDfB95on0%2Fs9FCkkL3viF2ZHu7D5uTiK%2Fimg.png&quot; data-filename=&quot;이미지 4.png&quot; data-origin-width=&quot;914&quot; data-origin-height=&quot;552&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;SSH 서비스를 재시작 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1586230756403&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$&amp;gt; sudo service sshd restart&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;SSH 접속을 종료한 후 다시 접속을 시도해보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;이미지 5.png&quot; data-origin-width=&quot;914&quot; data-origin-height=&quot;296&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HqfY3/btqDfAXHhMG/xf5UtNu2LcJ4a2zQB9We8K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HqfY3/btqDfAXHhMG/xf5UtNu2LcJ4a2zQB9We8K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HqfY3/btqDfAXHhMG/xf5UtNu2LcJ4a2zQB9We8K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHqfY3%2FbtqDfAXHhMG%2Fxf5UtNu2LcJ4a2zQB9We8K%2Fimg.png&quot; data-filename=&quot;이미지 5.png&quot; data-origin-width=&quot;914&quot; data-origin-height=&quot;296&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;접속이 거부되는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;그럼 변경된 포트를 이용해서 다시 접속해 보자. -p 옵션을 이용해서 변경한 포트번호인 9001을 입력한다.&lt;/p&gt;
&lt;pre id=&quot;code_1586230861488&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$&amp;gt; ssh -i LightsailDefaultKey-ap-northeast-2.pem ubuntu@13.209.31.173 -p 9001&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;변경한 포트번호를 이용해서 정상적으로 SSH 접속에 성공한 것을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;이미지 6.png&quot; data-origin-width=&quot;914&quot; data-origin-height=&quot;680&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BOLcS/btqDc4yTfj9/o9HGnDWQcxtwJFs5WdiPdk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BOLcS/btqDc4yTfj9/o9HGnDWQcxtwJFs5WdiPdk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BOLcS/btqDc4yTfj9/o9HGnDWQcxtwJFs5WdiPdk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBOLcS%2FbtqDc4yTfj9%2Fo9HGnDWQcxtwJFs5WdiPdk%2Fimg.png&quot; data-filename=&quot;이미지 6.png&quot; data-origin-width=&quot;914&quot; data-origin-height=&quot;680&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;새로운 포트를 이용해서 SSH 접속에 성공했으니 방화벽에서 기본 SSH 포트를 삭제하자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;이미지 7.png&quot; data-origin-width=&quot;1105&quot; data-origin-height=&quot;508&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bNBZUw/btqDfCgQ18G/OuREVkSmzQuCOgVHgmaJ9k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bNBZUw/btqDfCgQ18G/OuREVkSmzQuCOgVHgmaJ9k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bNBZUw/btqDfCgQ18G/OuREVkSmzQuCOgVHgmaJ9k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbNBZUw%2FbtqDfCgQ18G%2FOuREVkSmzQuCOgVHgmaJ9k%2Fimg.png&quot; data-filename=&quot;이미지 7.png&quot; data-origin-width=&quot;1105&quot; data-origin-height=&quot;508&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;한가지 유의할 점은 기본 SSH 포트를 삭제하면 Lightsail의 연결관리 페이지에서 브라우저를 이용한 SSH 접속이 더 이상 허용되지 않는다. 이 기능은 기본 SSH 포트를 이용하기 때문이다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;이제 SSH 기본 포트를 임의의 포트로 변경했기 때문에 봇을 이용한 SSH 접속 공격은 원천적으로 차단되었다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Development/Cloud computing</category>
      <author>독행소년</author>
      <guid isPermaLink="true">https://here4you.tistory.com/249</guid>
      <comments>https://here4you.tistory.com/249#entry249comment</comments>
      <pubDate>Tue, 7 Apr 2020 12:46:36 +0900</pubDate>
    </item>
    <item>
      <title>Lightsail 사용법 - 고정IP 할당 및 SSH 접속 방법 | 인스턴스 관리/설정</title>
      <link>https://here4you.tistory.com/248</link>
      <description>&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;지난 강좌에서는 Lightsail을 이용해서 우분투 인스턴스를 생성하는 방법에 대해서 알아봤다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;이번 시간에는 인스턴스를 관리설정하는 방법에 대해서 알아본다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;인스턴스의 메뉴아이콘을 누르면 다음과 같이 서브 메뉴들이 나타나는데 관리 메뉴를 클릭한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;이미지 14.png&quot; data-origin-width=&quot;1141&quot; data-origin-height=&quot;787&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cFVvyM/btqDc4LOyEo/SYvZSouZlYgtOcYDZ9snak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cFVvyM/btqDc4LOyEo/SYvZSouZlYgtOcYDZ9snak/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cFVvyM/btqDc4LOyEo/SYvZSouZlYgtOcYDZ9snak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcFVvyM%2FbtqDc4LOyEo%2FSYvZSouZlYgtOcYDZ9snak%2Fimg.png&quot; data-filename=&quot;이미지 14.png&quot; data-origin-width=&quot;1141&quot; data-origin-height=&quot;787&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;연결 관리 페이지가 나타난다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;이미지 15.png&quot; data-origin-width=&quot;1141&quot; data-origin-height=&quot;1206&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/343dE/btqDc5qybes/VMJCDzLKK8qXmk51fC44wK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/343dE/btqDc5qybes/VMJCDzLKK8qXmk51fC44wK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/343dE/btqDc5qybes/VMJCDzLKK8qXmk51fC44wK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F343dE%2FbtqDc5qybes%2FVMJCDzLKK8qXmk51fC44wK%2Fimg.png&quot; data-filename=&quot;이미지 15.png&quot; data-origin-width=&quot;1141&quot; data-origin-height=&quot;1206&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;인스턴스에 연결하는 방법은 두 가지다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;첫번째는 브라우저를 이용해서 연결하는 방법으로 쉽게 연결가능하지만 매번 브라우저상에서 터미널을 이용하는 것은 불편하다. 소스코드 편집을 브라우저 상에서 할 순 없지 않은가?&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;두번째 방법은 자체 SSH 클라이언트를 사용하여 연결하는 방법이다. 사용중인 로컬머신에서 SSH 클라이언트를 이용해서 우분투 인스턴스로 접속하는 방법이며 실제 개발에서는 이 방법이 주로 사용된다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;우선 화면을 보면 접속에 사용할 사용자 이름으로 ubuntu라고 설정된 것을 확인할 수 있다. 또 접속할 IP로 퍼블릭 IP가 하나 설정된 것을 확인할 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 Lightsail의 퍼블릭 IP는 인스턴스를 재시작하면 변경된다. 고정IP가 아니라는 뜻이다. 원활하게 SSH 접속을 하기 위해서는 인스턴스에 고정IP를 할당해야 한다. 이를 위해 네트워킹 항목으로 이동하자.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;네트워킹 관리화면은 다음과 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;이미지 16.png&quot; data-origin-width=&quot;1141&quot; data-origin-height=&quot;1072&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1yfSF/btqDeU9luZP/hsGW9pdq25sE7JpgJAmyhK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1yfSF/btqDeU9luZP/hsGW9pdq25sE7JpgJAmyhK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1yfSF/btqDeU9luZP/hsGW9pdq25sE7JpgJAmyhK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1yfSF%2FbtqDeU9luZP%2FhsGW9pdq25sE7JpgJAmyhK%2Fimg.png&quot; data-filename=&quot;이미지 16.png&quot; data-origin-width=&quot;1141&quot; data-origin-height=&quot;1072&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;우선 프라이빗 IP는 내부통신용 IP다. 만약 동일 리전(위치)에 복수의 인스턴스를 운영할 경우, 그리고 인스턴스들간에 통신이 필요할 경우 이 프라이빗 IP를 이용하는 것이 안전하고 더 빠르고 그리고 무료다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;방화벽 항목에는 외부 접속을 위한 SSH용 포트와 HTTP용 포트만이 오픈되어 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;마지막으로 퍼플릭 IP 항목은 조금전 연결 페이지에서 살펴본 그 IP 주소가 출력되고 있다. 고정 IP 생성 버튼을 클릭하자.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;인스턴스 연결에서 고정IP를 할당할 인스턴스를 선택하고, 그 고정 IP의 이름을 부여한 후 생성 버튼을 클릭한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;이미지 17.png&quot; data-origin-width=&quot;1141&quot; data-origin-height=&quot;1195&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cRTOHK/btqDcwV6enQ/rQ9Lr5qKRSazWI3yKx9Jz1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cRTOHK/btqDcwV6enQ/rQ9Lr5qKRSazWI3yKx9Jz1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cRTOHK/btqDcwV6enQ/rQ9Lr5qKRSazWI3yKx9Jz1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcRTOHK%2FbtqDcwV6enQ%2FrQ9Lr5qKRSazWI3yKx9Jz1%2Fimg.png&quot; data-filename=&quot;이미지 17.png&quot; data-origin-width=&quot;1141&quot; data-origin-height=&quot;1195&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;다음과 같이 고정 IP가 할당되었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;이미지 18.png&quot; data-origin-width=&quot;1141&quot; data-origin-height=&quot;858&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lUabg/btqDc4ZlJ1c/2KbYX4gQVxolYRtDBmszg1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lUabg/btqDc4ZlJ1c/2KbYX4gQVxolYRtDBmszg1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lUabg/btqDc4ZlJ1c/2KbYX4gQVxolYRtDBmszg1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlUabg%2FbtqDc4ZlJ1c%2F2KbYX4gQVxolYRtDBmszg1%2Fimg.png&quot; data-filename=&quot;이미지 18.png&quot; data-origin-width=&quot;1141&quot; data-origin-height=&quot;858&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;다시 인스턴스의 연결 관리 페이지로 진입해 보자. 퍼블릭 IP의 주소값이 고정 IP로 변경된 것을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;이미지 20.png&quot; data-origin-width=&quot;1141&quot; data-origin-height=&quot;1109&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/C01wb/btqDc3zoKv2/YH02pTfClpoQiLyxuWLvIk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/C01wb/btqDc3zoKv2/YH02pTfClpoQiLyxuWLvIk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/C01wb/btqDc3zoKv2/YH02pTfClpoQiLyxuWLvIk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FC01wb%2FbtqDc3zoKv2%2FYH02pTfClpoQiLyxuWLvIk%2Fimg.png&quot; data-filename=&quot;이미지 20.png&quot; data-origin-width=&quot;1141&quot; data-origin-height=&quot;1109&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;자 이제 로컬 머신에서 SSH 클라이언트를 이용해서 인스턴스로 접속해 볼 차례이다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;화면 하단에 보면 클라이언트와 연결할 때는 프라이빗 키도 필요하다고 안내되고 있고 계정 페이지에서 기본 프라이븐 킷을 다운로드할 수 있다고 한다. 계정 페이지로 이동하자.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;SSH 키 황목에서 보면 서울 리전의 기본 SSH 키를 다운로드할 수 있다. 다운로드 하자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;이미지 21.png&quot; data-origin-width=&quot;1040&quot; data-origin-height=&quot;865&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/psSdr/btqDeUaqpBT/A2wlXGpwzMtJGBbZyrrTrk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/psSdr/btqDeUaqpBT/A2wlXGpwzMtJGBbZyrrTrk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/psSdr/btqDeUaqpBT/A2wlXGpwzMtJGBbZyrrTrk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpsSdr%2FbtqDeUaqpBT%2FA2wlXGpwzMtJGBbZyrrTrk%2Fimg.png&quot; data-filename=&quot;이미지 21.png&quot; data-origin-width=&quot;1040&quot; data-origin-height=&quot;865&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;LightsailDefaultKey-ap-northeast-2.pem 이란 이름으로 다운로드 된다. 적당한 위치로 복사한 후 커맨트창을 열어 해당 위치로 이동한다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;참고로 본인이 사용하는 콘솔은 cmder이다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://cmder.net/&quot;&gt;https://cmder.net/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1586154226779&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Cmder | Console Emulator&quot; data-og-description=&quot;Total portability Carry it with you on a USB stick or in the Cloud, so your settings, aliases and history can go anywhere you go. You will not see that ugly Windows prompt ever again.&quot; data-og-host=&quot;cmder.net&quot; data-og-source-url=&quot;https://cmder.net/&quot; data-og-url=&quot;https://cmder.net/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/tBd7o/hyFzNrvSvw/1vIh8kqAgskkuA1WdfKdM0/img.png?width=1366&amp;amp;height=732&amp;amp;face=0_0_1366_732,https://scrap.kakaocdn.net/dn/c9FFta/hyFzQ9BIAP/xSpUPlL2AktBLejE0zqSuK/img.png?width=400&amp;amp;height=300&amp;amp;face=0_0_400_300,https://scrap.kakaocdn.net/dn/cj5TVq/hyFyveVuJX/E70iPH8BZbO7yEiHuqi94k/img.png?width=400&amp;amp;height=300&amp;amp;face=0_0_400_300&quot;&gt;&lt;a href=&quot;https://cmder.net/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://cmder.net/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/tBd7o/hyFzNrvSvw/1vIh8kqAgskkuA1WdfKdM0/img.png?width=1366&amp;amp;height=732&amp;amp;face=0_0_1366_732,https://scrap.kakaocdn.net/dn/c9FFta/hyFzQ9BIAP/xSpUPlL2AktBLejE0zqSuK/img.png?width=400&amp;amp;height=300&amp;amp;face=0_0_400_300,https://scrap.kakaocdn.net/dn/cj5TVq/hyFyveVuJX/E70iPH8BZbO7yEiHuqi94k/img.png?width=400&amp;amp;height=300&amp;amp;face=0_0_400_300');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Cmder | Console Emulator&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Total portability Carry it with you on a USB stick or in the Cloud, so your settings, aliases and history can go anywhere you go. You will not see that ugly Windows prompt ever again.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;cmder.net&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;다운로드한 SSH 키 파일이 존재한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;이미지 22.png&quot; data-origin-width=&quot;914&quot; data-origin-height=&quot;232&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GPkcT/btqDc30veq1/BZrgdVxBDvKODxvXr78COK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GPkcT/btqDc30veq1/BZrgdVxBDvKODxvXr78COK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GPkcT/btqDc30veq1/BZrgdVxBDvKODxvXr78COK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGPkcT%2FbtqDc30veq1%2FBZrgdVxBDvKODxvXr78COK%2Fimg.png&quot; data-filename=&quot;이미지 22.png&quot; data-origin-width=&quot;914&quot; data-origin-height=&quot;232&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;인스턴스에 접속하기 위해 다음과 같이 입력한다.&lt;/p&gt;
&lt;pre id=&quot;code_1586154400304&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$&amp;gt; ssh -i LightsailDefaultKey-ap-northeast-2.pem ubuntu@13.209.31.173&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;이미지 23.png&quot; data-origin-width=&quot;914&quot; data-origin-height=&quot;280&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BbFb7/btqDeInMZma/9cAT2pCKeYNNRA00QXyGp1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BbFb7/btqDeInMZma/9cAT2pCKeYNNRA00QXyGp1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BbFb7/btqDeInMZma/9cAT2pCKeYNNRA00QXyGp1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBbFb7%2FbtqDeInMZma%2F9cAT2pCKeYNNRA00QXyGp1%2Fimg.png&quot; data-filename=&quot;이미지 23.png&quot; data-origin-width=&quot;914&quot; data-origin-height=&quot;280&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;SSH 키 파일의 지문(fingerprint)를 이용해서 접속할 것인지를 묻는다. yes라고 입력한다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;헐. SSH 키 파일의 접근 권한 오류가 발생한다. 윈도우서 만나니 당황스럽다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;이미지 25.png&quot; data-origin-width=&quot;914&quot; data-origin-height=&quot;280&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dA8Kyy/btqDeInM6U0/SOoWz9368y3JW1kqDY5IJk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dA8Kyy/btqDeInM6U0/SOoWz9368y3JW1kqDY5IJk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dA8Kyy/btqDeInM6U0/SOoWz9368y3JW1kqDY5IJk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdA8Kyy%2FbtqDeInM6U0%2FSOoWz9368y3JW1kqDY5IJk%2Fimg.png&quot; data-filename=&quot;이미지 25.png&quot; data-origin-width=&quot;914&quot; data-origin-height=&quot;280&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;키 파일의 권한을 변경해 보자.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;리눅스의 경우 chmod 400 으로 권한을 변경해서 사용하면 되는데 윈도우의 경우 chmod 명령어가 없으므로 다른 방법을 찾아봐야 한다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;키 파일이 저장된 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;u&gt;&lt;b&gt;폴더&lt;/b&gt;&lt;/u&gt;&lt;/span&gt;의 속성을 변경한다. (본인의 경우 ssh 폴더)&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;속성-&amp;gt;보안-&amp;gt;고급 순으로 이동하면 다음의 창이 나타난다. Admonistrators를 선택하고 화면 하단의 상속사용 안함을 선택한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;이미지 32.png&quot; data-origin-width=&quot;895&quot; data-origin-height=&quot;595&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bp6jjF/btqDeJfXgxx/zBuibcb4fJP1y31zofSEf1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bp6jjF/btqDeJfXgxx/zBuibcb4fJP1y31zofSEf1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bp6jjF/btqDeJfXgxx/zBuibcb4fJP1y31zofSEf1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbp6jjF%2FbtqDeJfXgxx%2FzBuibcb4fJP1y31zofSEf1%2Fimg.png&quot; data-filename=&quot;이미지 32.png&quot; data-origin-width=&quot;895&quot; data-origin-height=&quot;595&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;하단의 이 개체에서 상속된 사용 권한을 모두 제거합니다. 를 선택한 후 적용 후 확인을 선택한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;이미지 33.png&quot; data-origin-width=&quot;542&quot; data-origin-height=&quot;277&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/boUJNu/btqDeU2ECpO/blUBstRRqJagd5pSedNXA1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/boUJNu/btqDeU2ECpO/blUBstRRqJagd5pSedNXA1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/boUJNu/btqDeU2ECpO/blUBstRRqJagd5pSedNXA1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FboUJNu%2FbtqDeU2ECpO%2FblUBstRRqJagd5pSedNXA1%2Fimg.png&quot; data-filename=&quot;이미지 33.png&quot; data-origin-width=&quot;542&quot; data-origin-height=&quot;277&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;ssh 폴더로 집입하려고 하면 액세스할 권한이 없다는 문구가 나온다. 계속(관리자계정)을 선택해서 진입한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;이미지 35.png&quot; data-origin-width=&quot;352&quot; data-origin-height=&quot;185&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmscZv/btqDbEUIPFX/kiWCEGsL8vzlqq7bYVFxS0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmscZv/btqDbEUIPFX/kiWCEGsL8vzlqq7bYVFxS0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmscZv/btqDbEUIPFX/kiWCEGsL8vzlqq7bYVFxS0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmscZv%2FbtqDbEUIPFX%2FkiWCEGsL8vzlqq7bYVFxS0%2Fimg.png&quot; data-filename=&quot;이미지 35.png&quot; data-origin-width=&quot;352&quot; data-origin-height=&quot;185&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;이제 터미널에서도 ssh 폴더로 진입이 가능하다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;이미지 36.png&quot; data-origin-width=&quot;914&quot; data-origin-height=&quot;280&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/epzPoO/btqDcdP7hGZ/40D1EehwUGkixVoK1BEwM0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/epzPoO/btqDcdP7hGZ/40D1EehwUGkixVoK1BEwM0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/epzPoO/btqDcdP7hGZ/40D1EehwUGkixVoK1BEwM0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FepzPoO%2FbtqDcdP7hGZ%2F40D1EehwUGkixVoK1BEwM0%2Fimg.png&quot; data-filename=&quot;이미지 36.png&quot; data-origin-width=&quot;914&quot; data-origin-height=&quot;280&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;다시 ssh로 인스턴스에 접근해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1586158037346&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$&amp;gt; ssh -i LightsailDefaultKey-ap-northeast-2.pem ubuntu@13.209.31.173&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;드디어 접속에 성공했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;이미지 37.png&quot; data-origin-width=&quot;914&quot; data-origin-height=&quot;776&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VwSgv/btqDcx1Scf5/wFQz8XfGtrv4Vgo7QLgUP0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VwSgv/btqDcx1Scf5/wFQz8XfGtrv4Vgo7QLgUP0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VwSgv/btqDcx1Scf5/wFQz8XfGtrv4Vgo7QLgUP0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVwSgv%2FbtqDcx1Scf5%2FwFQz8XfGtrv4Vgo7QLgUP0%2Fimg.png&quot; data-filename=&quot;이미지 37.png&quot; data-origin-width=&quot;914&quot; data-origin-height=&quot;776&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;이제 로컬머신에서 인스턴스로 접속하고자 할 때 Lightsail 사이트에 접속하지 않고도 SSH를 이용해서 접근가능해졌다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;사족!&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;일반적으로 SSH로 원격 머신에 접속할 때에는 ID와 비밀번호 그리고 호스트 주소가 필요하다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 이 방식은 굉장히 위험하다. 이러한 방식을 지원하는 클라우드 서버의 경우 인스턴스를 생성하자마자 SSH 접속 요청이 쏟아져 들어온다. 본인도 이러한 공격으로 인해 여러번 서버를 날려먹은 적이 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;이에 반해 SSH 키를 이용하는 방식은 키값을 가지지 않는 접속요청(다시 말에 ID와 비번을 이용한 접속요청)을 원천차단하기 때문에 보다 안전하다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;다음 시간에는 SSH 포트 설정에 대해서 알아본다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;[update 2021-08-26]&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;퍼미션을 변경한 pem파일은 퍼미션을 변경한 폴더에서만 요효한 것 같다. 해당 파일을 다른 폴더로 복사하지 말고 해당 폴더에서 바로 이용해야 한다.&lt;/p&gt;</description>
      <category>Development/Cloud computing</category>
      <author>독행소년</author>
      <guid isPermaLink="true">https://here4you.tistory.com/248</guid>
      <comments>https://here4you.tistory.com/248#entry248comment</comments>
      <pubDate>Mon, 6 Apr 2020 16:32:54 +0900</pubDate>
    </item>
    <item>
      <title>Lightsail 사용법 - 우분투 인스턴스 설치</title>
      <link>https://here4you.tistory.com/247</link>
      <description>&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;Lightsail은 AWS에서 제공하는 가상 서버 서비스로 간단한 웹 어플리케이션이나 웹 서비스 등을 개발/운영할 수 있다. 기존 AWS의 E2C와 비교하면 훨씬 쉽고 간단하며 가격도 저렴하다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;이번 시간에는 Lightsail를 이용해서 우분투를 설치하는 방법에 대해서 알아본다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;b&gt;1. Lightsail 가입&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;Lightsail 사이트를 방문해서 회원 가입을 한다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;a href=&quot;https://lightsail.aws.amazon.com/&quot;&gt;https://lightsail.aws.amazon.com/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1586150822844&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;website&quot; data-og-title=&quot;https://lightsail.aws.amazon.com/ls/webapp&quot; data-og-description=&quot;&quot; data-og-host=&quot;lightsail.aws.amazon.com&quot; data-og-source-url=&quot;https://lightsail.aws.amazon.com/&quot; data-og-url=&quot;https://lightsail.aws.amazon.com/ls/webapp&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://lightsail.aws.amazon.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://lightsail.aws.amazon.com/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;https://lightsail.aws.amazon.com/ls/webapp&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;lightsail.aws.amazon.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;b&gt;2. 상품 선택&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;회원가입을 완료하면 다음과 같은 화면을 만나게 된다. 아직 아무런 인스턴스도 존재하지 않는다. 인스턴스 생성을 선택한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;이미지 11.png&quot; data-origin-width=&quot;1141&quot; data-origin-height=&quot;1105&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bEfUmn/btqDc3F7e93/ieyFJoC3CvV2wXvFoNI4W1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bEfUmn/btqDc3F7e93/ieyFJoC3CvV2wXvFoNI4W1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bEfUmn/btqDc3F7e93/ieyFJoC3CvV2wXvFoNI4W1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbEfUmn%2FbtqDc3F7e93%2FieyFJoC3CvV2wXvFoNI4W1%2Fimg.png&quot; data-filename=&quot;이미지 11.png&quot; data-origin-width=&quot;1141&quot; data-origin-height=&quot;1105&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;인스턴스 생성 화면은 다음과 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;이미지 2.png&quot; data-origin-width=&quot;1141&quot; data-origin-height=&quot;1174&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pOuRQ/btqDdIobJwa/OLqzBIAUkdJu2qZm6cexfk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pOuRQ/btqDdIobJwa/OLqzBIAUkdJu2qZm6cexfk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pOuRQ/btqDdIobJwa/OLqzBIAUkdJu2qZm6cexfk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpOuRQ%2FbtqDdIobJwa%2FOLqzBIAUkdJu2qZm6cexfk%2Fimg.png&quot; data-filename=&quot;이미지 2.png&quot; data-origin-width=&quot;1141&quot; data-origin-height=&quot;1174&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;b&gt;인스턴스 위치&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;인스턴스의 위치는 말 그대로 인스턴스를 생성할 위치를 선택하는 것이며, 기본으로 한국이 설정되어 있다. 만약 외국의 특정 지역을 대상으로 서비스하고자 한다면 해당 위치와 간단한 위치로 변경하는 것이 당연히 유리하다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;b&gt;인스턴스 이미지 선택&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;선택 가능한 이미지로는 Linux/Unix 계열과 MS의 윈도우 계열이 있다. 우분투를 설치할 것이므로 Linux/Unix를 선택한다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;블루프린트 항목에서는 앱+OS와 OS 전용 중 하나를 선택할 수 있으며, 운분투를 직접 설치할 것이므로 OS 전용으로 선택한다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;OS 전용으로 선택한 후 우분투 18.04 LTS를 선택한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;이미지 3.png&quot; data-origin-width=&quot;1141&quot; data-origin-height=&quot;609&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/r1Dv8/btqDc5cWJdT/SqczQ1jwAzrhvyvQwMX8Pk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/r1Dv8/btqDc5cWJdT/SqczQ1jwAzrhvyvQwMX8Pk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/r1Dv8/btqDc5cWJdT/SqczQ1jwAzrhvyvQwMX8Pk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fr1Dv8%2FbtqDc5cWJdT%2FSqczQ1jwAzrhvyvQwMX8Pk%2Fimg.png&quot; data-filename=&quot;이미지 3.png&quot; data-origin-width=&quot;1141&quot; data-origin-height=&quot;609&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;선택 사항은 일단 생략하고 넘어간다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;이미지 6.png&quot; data-origin-width=&quot;1141&quot; data-origin-height=&quot;463&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uKcLy/btqDceBlD2c/BaZy7DTAocdDj6hG7zX4lk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uKcLy/btqDceBlD2c/BaZy7DTAocdDj6hG7zX4lk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uKcLy/btqDceBlD2c/BaZy7DTAocdDj6hG7zX4lk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuKcLy%2FbtqDceBlD2c%2FBaZy7DTAocdDj6hG7zX4lk%2Fimg.png&quot; data-filename=&quot;이미지 6.png&quot; data-origin-width=&quot;1141&quot; data-origin-height=&quot;463&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;인스턴스 플랜을 선택해야 한다. 가장 저렴하고 첫 달은 무료인 3.5불짜리 인스턴스로 선택한다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;* 실제 개발을 하다보면 수없이 인스턴스를 삭제하고 생성한다. 미리 좋은 걸 선택할 필요가 없다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;이미지 8.png&quot; data-origin-width=&quot;1141&quot; data-origin-height=&quot;714&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgbHuj/btqDf1tm2U1/Mq4cAXoK33UoQoE4nNkiN1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgbHuj/btqDf1tm2U1/Mq4cAXoK33UoQoE4nNkiN1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgbHuj/btqDf1tm2U1/Mq4cAXoK33UoQoE4nNkiN1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgbHuj%2FbtqDf1tm2U1%2FMq4cAXoK33UoQoE4nNkiN1%2Fimg.png&quot; data-filename=&quot;이미지 8.png&quot; data-origin-width=&quot;1141&quot; data-origin-height=&quot;714&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;생성할 인스턴스에 이름을 부여한 후 인스턴스 생성을 클릭한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;이미지 9.png&quot; data-origin-width=&quot;1141&quot; data-origin-height=&quot;787&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbQqUu/btqDf2Z7FXg/9FaLwvcFM9Nk6m7QH9JPL0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbQqUu/btqDf2Z7FXg/9FaLwvcFM9Nk6m7QH9JPL0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbQqUu/btqDf2Z7FXg/9FaLwvcFM9Nk6m7QH9JPL0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbQqUu%2FbtqDf2Z7FXg%2F9FaLwvcFM9Nk6m7QH9JPL0%2Fimg.png&quot; data-filename=&quot;이미지 9.png&quot; data-origin-width=&quot;1141&quot; data-origin-height=&quot;787&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;자 새로운 우분투 인스턴스인 FirstUbuntu 인스턴스가 생성되고 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;이미지 10.png&quot; data-origin-width=&quot;1141&quot; data-origin-height=&quot;787&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GFgDZ/btqDbFTwMdR/JQ0xK3ZvyscKDRuJVoDLZ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GFgDZ/btqDbFTwMdR/JQ0xK3ZvyscKDRuJVoDLZ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GFgDZ/btqDbFTwMdR/JQ0xK3ZvyscKDRuJVoDLZ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGFgDZ%2FbtqDbFTwMdR%2FJQ0xK3ZvyscKDRuJVoDLZ0%2Fimg.png&quot; data-filename=&quot;이미지 10.png&quot; data-origin-width=&quot;1141&quot; data-origin-height=&quot;787&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;약 10여초만에 생성이 완료되었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;이미지 11.png&quot; data-origin-width=&quot;1141&quot; data-origin-height=&quot;787&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Gel3s/btqDeJG1e0y/fL2p0XDO4hXE82mQrAqLBK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Gel3s/btqDeJG1e0y/fL2p0XDO4hXE82mQrAqLBK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Gel3s/btqDeJG1e0y/fL2p0XDO4hXE82mQrAqLBK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGel3s%2FbtqDeJG1e0y%2FfL2p0XDO4hXE82mQrAqLBK%2Fimg.png&quot; data-filename=&quot;이미지 11.png&quot; data-origin-width=&quot;1141&quot; data-origin-height=&quot;787&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;정말 쉽고 빠르지 않은가?&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;인스턴스의 프로프트 아이콘을 클릭하면 우분투 인스턴스의 터미널로 접속할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;이미지 12.png&quot; data-origin-width=&quot;1026&quot; data-origin-height=&quot;884&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/du6GH8/btqDfBuWmta/BWZCgSG9D61tGJe18t40s1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/du6GH8/btqDfBuWmta/BWZCgSG9D61tGJe18t40s1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/du6GH8/btqDfBuWmta/BWZCgSG9D61tGJe18t40s1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdu6GH8%2FbtqDfBuWmta%2FBWZCgSG9D61tGJe18t40s1%2Fimg.png&quot; data-filename=&quot;이미지 12.png&quot; data-origin-width=&quot;1026&quot; data-origin-height=&quot;884&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;이렇게 Lightsail을 이용하면 1~2분만에 우분투 머신을 만들어서 이용할 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Development/Cloud computing</category>
      <author>독행소년</author>
      <guid isPermaLink="true">https://here4you.tistory.com/247</guid>
      <comments>https://here4you.tistory.com/247#entry247comment</comments>
      <pubDate>Mon, 6 Apr 2020 14:46:48 +0900</pubDate>
    </item>
    <item>
      <title>Flutter Example - ListView | Handle Items | Change items</title>
      <link>https://here4you.tistory.com/246</link>
      <description>&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1585016731522&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:flutter/material.dart';

ListViewHandelItem2State pageState;

class ListViewHandelItem2 extends StatefulWidget {
  @override
  ListViewHandelItem2State createState() {
    pageState = ListViewHandelItem2State();
    return pageState;
  }
}

class ListViewHandelItem2State extends State&amp;lt;ListViewHandelItem2&amp;gt; {
  List&amp;lt;String&amp;gt; items = List&amp;lt;String&amp;gt;.generate(7, (index) {
    return &quot;Item - $index&quot;;
  });

  TextEditingController insertCon = TextEditingController(
    text: &quot;good&quot;,
  );
  TextEditingController changeCon = TextEditingController();
  int selectedIndex;

  @override
  void dispose() {
    insertCon.dispose();
    changeCon.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(&quot;ListView Handle Items2&quot;)),
      body: Column(
        children: &amp;lt;Widget&amp;gt;[
          Padding(
            padding: const EdgeInsets.all(8.0),
            child: Container(
              padding: const EdgeInsets.all(10),
              height: 70,
              alignment: Alignment(0, 0),
              color: Colors.orange,
              child: Text(
                &quot;To remove an item, swipe the tile to the right or tap the trash icon.&quot;,
                style: TextStyle(color: Colors.white),
              ),
            ),
          ),
          Expanded(
            child: ListView.builder(
              itemCount: items.length,
              itemBuilder: (context, index) {
                final item = items[index];
                return Dismissible(
                  key: Key(item),
                  direction: DismissDirection.startToEnd,
                  child: InkWell(
                    child: ListTile(
                      title: Text(item),
                      trailing: IconButton(
                        icon: Icon(Icons.delete_forever),
                        onPressed: () {
                          setState(() {
                            items.removeAt(index);
                          });
                        },
                      ),
                    ),
                    onTap: () {
                      selectedIndex = index;
                      changeCon.text = items[index];
                    },
                  ),
                  onDismissed: (direction) {
                    setState(() {
                      items.removeAt(index);
                    });
                  },
                );
              },
            ),
          ),
          Divider(
            color: Colors.grey,
            height: 5,
            indent: 10,
            endIndent: 10,
          ),
          Padding(
            padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10),
            child: Row(
              children: &amp;lt;Widget&amp;gt;[
                Text(&quot;Change Item:&quot;),
                Expanded(
                  child: Padding(
                    padding: const EdgeInsets.symmetric(horizontal: 20),
                    child: TextField(
                      controller: changeCon,
                      onSubmitted: (text) {
                        _changeItem();
                      },
                    ),
                  ),
                ),
                RaisedButton(
                  child: Text(&quot;Change&quot;),
                  onPressed: () {
                    _changeItem();
                  },
                )
              ],
            ),
          ),
          Padding(
            padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10),
            child: Row(
              children: &amp;lt;Widget&amp;gt;[
                Text(&quot;Inser Item:&quot;),
                Expanded(
                  child: Padding(
                    padding: const EdgeInsets.symmetric(horizontal: 20),
                    child: TextField(
                      controller: insertCon,
                      onSubmitted: (text) {
                        setState(() {
                          if (insertCon.text != &quot;&quot;) {
                            items.add(insertCon.text);
                          }
                        });
                        insertCon.clear();
                      },
                    ),
                  ),
                ),
                RaisedButton(
                  child: Text(&quot;Insert&quot;),
                  onPressed: () {
                    setState(() {
                      if (insertCon.text != &quot;&quot;) {
                        items.add(insertCon.text);
                      }
                    });
                    insertCon.clear();
                  },
                )
              ],
            ),
          ),
        ],
      ),
    );
  }

  void _changeItem() {
    if (selectedIndex == null || changeCon.text.isEmpty) {
      return;
    }
    print(&quot;selectedIndex: $selectedIndex, changedText: ${changeCon.text}&quot;);
    setState(() {
      items[selectedIndex] = changeCon.text;
    });
    selectedIndex = null;
    changeCon.text = &quot;&quot;;

  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;anigif.gif&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;800&quot; width=&quot;300&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bSSgsa/btqCUdvy8R5/vdrGDZwzrdzEhPqWNsg9Ek/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bSSgsa/btqCUdvy8R5/vdrGDZwzrdzEhPqWNsg9Ek/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bSSgsa/btqCUdvy8R5/vdrGDZwzrdzEhPqWNsg9Ek/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bSSgsa/btqCUdvy8R5/vdrGDZwzrdzEhPqWNsg9Ek/img.gif&quot; data-filename=&quot;anigif.gif&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;800&quot; width=&quot;300&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://here4you.tistory.com/213&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;▶ Go to Table of Contents | 강의 목차로 이동&lt;/a&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;p style=&quot;font-size: 0.94em;&quot;&gt;&lt;i&gt;※ This example is also available in the &lt;a href=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Flutter Code Examples&lt;/a&gt; app. | 본 예제는 &lt;a href=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Flutter Code Examples&lt;/a&gt; 앱에서도 제공됩니다.&lt;/i&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1571728595725&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Flutter Code Examples - Google Play 앱&quot; data-og-description=&quot;Are you a beginner at Flutter? Check out the various features of Flutter through the demo. Source code for all demos is also provided.&quot; data-og-host=&quot;play.google.com&quot; data-og-source-url=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&quot; data-og-url=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&amp;amp;hl=ko&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cKyXFQ/hyDk1N2SdW/nmE1hqakZBSAtQsXkpb6o0/img.png?width=512&amp;amp;height=250&amp;amp;face=0_0_512_250,https://scrap.kakaocdn.net/dn/bS5MWV/hyDkS4EWPd/KVJotgZJcZew3MSlnxMAs1/img.png?width=600&amp;amp;height=300&amp;amp;face=0_0_600_300&quot;&gt;&lt;a href=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&amp;amp;hl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cKyXFQ/hyDk1N2SdW/nmE1hqakZBSAtQsXkpb6o0/img.png?width=512&amp;amp;height=250&amp;amp;face=0_0_512_250,https://scrap.kakaocdn.net/dn/bS5MWV/hyDkS4EWPd/KVJotgZJcZew3MSlnxMAs1/img.png?width=600&amp;amp;height=300&amp;amp;face=0_0_600_300');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;Flutter Code Examples - Google Play 앱&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;Are you a beginner at Flutter? Check out the various features of Flutter through the demo. Source code for all demos is also provided.&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;play.google.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;</description>
      <category>Tutorial/Flutter with App</category>
      <author>독행소년</author>
      <guid isPermaLink="true">https://here4you.tistory.com/246</guid>
      <comments>https://here4you.tistory.com/246#entry246comment</comments>
      <pubDate>Tue, 24 Mar 2020 11:30:59 +0900</pubDate>
    </item>
    <item>
      <title>Flutter Example - How to set the volume | volume plugin</title>
      <link>https://here4you.tistory.com/245</link>
      <description>&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;* About plugin&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://pub.dev/packages/volume&quot;&gt;https://pub.dev/packages/volume&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1584934331514&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;website&quot; data-og-title=&quot;volume | Flutter Package&quot; data-og-description=&quot;Volume plugin to control device VOLUME (Android only). Pull request for IOS implementation is welcome.&quot; data-og-host=&quot;pub.dev&quot; data-og-source-url=&quot;https://pub.dev/packages/volume&quot; data-og-url=&quot;https://pub.dev/packages/volume&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/chLNog/hyFn8wterO/gr2e1pEXbdX7wDBNjKmSa1/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400,https://scrap.kakaocdn.net/dn/cdwzCU/hyFn4niUOS/0GwRztTxngK7KZPqZbYjRK/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400&quot;&gt;&lt;a href=&quot;https://pub.dev/packages/volume&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://pub.dev/packages/volume&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/chLNog/hyFn8wterO/gr2e1pEXbdX7wDBNjKmSa1/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400,https://scrap.kakaocdn.net/dn/cdwzCU/hyFn4niUOS/0GwRztTxngK7KZPqZbYjRK/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;volume | Flutter Package&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;Volume plugin to control device VOLUME (Android only). Pull request for IOS implementation is welcome.&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;pub.dev&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;1. Add this to pubspec.yaml&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1584934394000&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dependencies:
  volume: ^0.1.0&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;2. Source Code&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1584934428198&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:flutter/material.dart';
import 'dart:async';
import 'package:volume/volume.dart';

void main() =&amp;gt; runApp(VolumePage());

class VolumePage extends StatefulWidget {
  @override
  _VolumePageState createState() =&amp;gt; _VolumePageState();
}

class _VolumePageState extends State&amp;lt;VolumePage&amp;gt; {
  AudioManager audioManager;
  int maxVol, currentVol;

  @override
  void initState() {
    super.initState();
    audioManager = AudioManager.STREAM_SYSTEM;
    initPlatformState(AudioManager.STREAM_VOICE_CALL);
    updateVolumes();
  }

  Future&amp;lt;void&amp;gt; initPlatformState(AudioManager am) async {
    await Volume.controlVolume(am);
  }

  void updateVolumes() async {
    // get Max Volume
    maxVol = await Volume.getMaxVol;
    // get Current Volume
    currentVol = await Volume.getVol;
    print(&quot;maxVol: $maxVol, currentVol: $currentVol&quot;);
    setState(() {});
  }

  void setVol(int i) async {
    await Volume.setVol(i);
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Volume Plugin example app'),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: &amp;lt;Widget&amp;gt;[
              Padding(
                padding: const EdgeInsets.symmetric(vertical: 20),
                child: Row(
                  mainAxisAlignment: MainAxisAlignment.spaceAround,
                  children: &amp;lt;Widget&amp;gt;[
                    Text(&quot;Max Volume: $maxVol&quot;),
                    Text(&quot;Current Volume: $currentVol&quot;)
                  ],
                ),
              ),
              DropdownButton(
                value: audioManager,
                items: [
                  DropdownMenuItem(
                    child: Text(&quot;In Call Volume&quot;),
                    value: AudioManager.STREAM_VOICE_CALL,
                  ),
                  DropdownMenuItem(
                    child: Text(&quot;System Volume&quot;),
                    value: AudioManager.STREAM_SYSTEM,
                  ),
                  DropdownMenuItem(
                    child: Text(&quot;Ring Volume&quot;),
                    value: AudioManager.STREAM_RING,
                  ),
                  DropdownMenuItem(
                    child: Text(&quot;Media Volume&quot;),
                    value: AudioManager.STREAM_MUSIC,
                  ),
                  DropdownMenuItem(
                    child: Text(&quot;Alarm volume&quot;),
                    value: AudioManager.STREAM_ALARM,
                  ),
                  DropdownMenuItem(
                    child: Text(&quot;Notifications Volume&quot;),
                    value: AudioManager.STREAM_NOTIFICATION,
                  ),
                ],
                isDense: true,
                onChanged: (AudioManager am) async {
                  print(am.toString());
                  initPlatformState(am);
                  updateVolumes();
                },
              ),
              (currentVol != null || maxVol != null)
                  ? Slider(
                      value: currentVol / 1.0,
                      divisions: maxVol,
                      max: maxVol / 1.0,
                      min: 0,
                      onChanged: (double d) {
                        setVol(d.toInt());
                        updateVolumes();
                      },
                    )
                  : Container(),
            ],
          ),
        ),
      ),
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;anigif.gif&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;800&quot; width=&quot;300&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bIdSj1/btqCQoSguwM/TKhElgLowMPUkBm0Ah5Cn0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bIdSj1/btqCQoSguwM/TKhElgLowMPUkBm0Ah5Cn0/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bIdSj1/btqCQoSguwM/TKhElgLowMPUkBm0Ah5Cn0/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bIdSj1/btqCQoSguwM/TKhElgLowMPUkBm0Ah5Cn0/img.gif&quot; data-filename=&quot;anigif.gif&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;800&quot; width=&quot;300&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Tutorial/Flutter with App</category>
      <author>독행소년</author>
      <guid isPermaLink="true">https://here4you.tistory.com/245</guid>
      <comments>https://here4you.tistory.com/245#entry245comment</comments>
      <pubDate>Mon, 23 Mar 2020 12:38:11 +0900</pubDate>
    </item>
    <item>
      <title>Flutter 강좌 - [Tip] image_picker 플러그인을 이용해서 갤러리 이미지를 읽어오지 못할 때 해결 법 | Fail to pick image using image_picker plugin</title>
      <link>https://here4you.tistory.com/244</link>
      <description>&lt;p style=&quot;font-size: 1.12em; text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em; text-align: justify;&quot;&gt;&lt;b&gt;Flutter Code Examples 강좌를 추천합니다.&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;제 블로그에서 Flutter Code Examples 프로젝트를 시작합니다.&lt;/li&gt;
&lt;li&gt;Flutter의 다양한 예제를 소스코드와 실행화면으로 제공합니다.&lt;/li&gt;
&lt;li&gt;또한 모든 예제는 &lt;a href=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Flutter Code Examples&lt;/a&gt; 앱을 통해 테스트 가능합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;a href=&quot;https://here4you.tistory.com/category/Tutorial/Flutter%20with%20App&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Flutter Code Examples 강좌로 메뉴로 이동&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;a href=&quot;https://here4you.tistory.com/213&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Flutter Code Examples 강좌 목록 페이지로 이동&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;a href=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Flutter Code Examples 앱 설치 | Google Play Store로 이동&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1571794790206&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Flutter Code Examples - Google Play 앱&quot; data-og-description=&quot;Are you a beginner at Flutter? Check out the various features of Flutter through the demo. Source code for all demos is also provided.&quot; data-og-host=&quot;play.google.com&quot; data-og-source-url=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&quot; data-og-url=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&amp;amp;hl=ko&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/czLd1P/hyDnjfJW0L/qDPYD8aFvfFMc98kyKiLK0/img.png?width=512&amp;amp;height=250&amp;amp;face=0_0_512_250,https://scrap.kakaocdn.net/dn/McaI6/hyDnmQ5H8S/TnzOISQwiEcJCDXG9Vbo71/img.png?width=600&amp;amp;height=300&amp;amp;face=0_0_600_300&quot;&gt;&lt;a href=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&amp;amp;hl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/czLd1P/hyDnjfJW0L/qDPYD8aFvfFMc98kyKiLK0/img.png?width=512&amp;amp;height=250&amp;amp;face=0_0_512_250,https://scrap.kakaocdn.net/dn/McaI6/hyDnmQ5H8S/TnzOISQwiEcJCDXG9Vbo71/img.png?width=600&amp;amp;height=300&amp;amp;face=0_0_600_300');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;Flutter Code Examples - Google Play 앱&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;Are you a beginner at Flutter? Check out the various features of Flutter through the demo. Source code for all demos is also provided.&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;play.google.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;hr style=&quot;margin: 20px auto 0px; border: none; cursor: pointer !important; z-index: 1; font-size: 0px; line-height: 0; height: 20px; background: url('../image/divider-line.svg') 0px -180px / 200px 200px no-repeat; width: 200px; color: #333333; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;blockquote class=&quot;tx-quote-tistory&quot;&gt;
&lt;p&gt;Flutter 강좌 시즌2 목록 :&amp;nbsp;&lt;a class=&quot;tx-link&quot; href=&quot;https://here4you.tistory.com/149&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://here4you.tistory.com/149&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;Flutter 앱을 개발하면서 갤러리나 카메라로부터 이미지를 읽어오고자 할 때 사용할 수 있는 플러그인으로 image_picker가 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;a href=&quot;https://pub.dev/packages/image_picker&quot;&gt;https://pub.dev/packages/image_picker&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1584070271881&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;website&quot; data-og-title=&quot;image_picker | Flutter Package&quot; data-og-description=&quot;Flutter plugin for selecting images from the Android and iOS image library, and taking new pictures with the camera.&quot; data-og-host=&quot;pub.dev&quot; data-og-source-url=&quot;https://pub.dev/packages/image_picker&quot; data-og-url=&quot;https://pub.dev/packages/image_picker&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bs2XrU/hyFhDQbrKs/9KqXlSNrw0cObJbekyz80K/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400,https://scrap.kakaocdn.net/dn/f9VHK/hyFhP392bM/ZdPhaN4sKqZmByQ7UyIvK1/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400&quot;&gt;&lt;a href=&quot;https://pub.dev/packages/image_picker&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://pub.dev/packages/image_picker&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bs2XrU/hyFhDQbrKs/9KqXlSNrw0cObJbekyz80K/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400,https://scrap.kakaocdn.net/dn/f9VHK/hyFhP392bM/ZdPhaN4sKqZmByQ7UyIvK1/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;image_picker | Flutter Package&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;Flutter plugin for selecting images from the Android and iOS image library, and taking new pictures with the camera.&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;pub.dev&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;사용법은 아래 강좌들에서 확인할 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;a href=&quot;https://here4you.tistory.com/196&quot;&gt;https://here4you.tistory.com/196&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1584071466930&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Flutter Example - Handle Image | Load Image from Gallery |   image_picker plugin&quot; data-og-description=&quot;* About Plugin https://pub.dev/packages/image_picker image_picker | Flutter Package Flutter plugin for selecting images from the Android and iOS image library, and taking new pictures with the camer..&quot; data-og-host=&quot;here4you.tistory.com&quot; data-og-source-url=&quot;https://here4you.tistory.com/196&quot; data-og-url=&quot;https://here4you.tistory.com/196&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/Ygpox/hyFhSfv9Kd/BL77Id1CU9D2xGeuVmBr20/img.gif?width=369&amp;amp;height=800&amp;amp;face=0_0_369_800,https://scrap.kakaocdn.net/dn/Vj3VY/hyFgtaAJPP/0d5LyF6kw2WKf2AxozYLp1/img.gif?width=369&amp;amp;height=800&amp;amp;face=0_0_369_800,https://scrap.kakaocdn.net/dn/kOupD/hyFgmoZltZ/Xb15eeHXSFikj32Ik3SEXk/img.jpg?width=765&amp;amp;height=765&amp;amp;face=245_215_460_449&quot;&gt;&lt;a href=&quot;https://here4you.tistory.com/196&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://here4you.tistory.com/196&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/Ygpox/hyFhSfv9Kd/BL77Id1CU9D2xGeuVmBr20/img.gif?width=369&amp;amp;height=800&amp;amp;face=0_0_369_800,https://scrap.kakaocdn.net/dn/Vj3VY/hyFgtaAJPP/0d5LyF6kw2WKf2AxozYLp1/img.gif?width=369&amp;amp;height=800&amp;amp;face=0_0_369_800,https://scrap.kakaocdn.net/dn/kOupD/hyFgmoZltZ/Xb15eeHXSFikj32Ik3SEXk/img.jpg?width=765&amp;amp;height=765&amp;amp;face=245_215_460_449');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;Flutter Example - Handle Image | Load Image from Gallery | image_picker plugin&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;* About Plugin https://pub.dev/packages/image_picker image_picker | Flutter Package Flutter plugin for selecting images from the Android and iOS image library, and taking new pictures with the camer..&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;here4you.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;본인도 이 플러그인을 이용해서 앱을 출시했는데 갤러리로부터 이미지를 읽어오지 못하는 문제가 발생한다는 피드백이 있었다.&amp;nbsp; 개발 중에 한번도 발생하지 않았던 문제였는데, 조사 중에 안드로이드 10에서만 발생하는 것을 알게되었다. 불행이도 본인이 가지고 있는 환경은 모두 안드로이드 9이었던 것이다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;아마도 안드로이드 10의 외부 저장소 접근 정책 때문인 것 같다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;해결 방법은 의외로 간단한다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;AndroidManifest.xml 파일에 Application 항목에 &lt;span&gt;requestLegacyExternalStorage&lt;/span&gt;&lt;span&gt;=&quot;true&quot; 속성을 추가하는 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1584071596526&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;						...
	&amp;lt;application
        android:name=&quot;io.flutter.app.FlutterApplication&quot;
        android:icon=&quot;@mipmap/ic_launcher&quot;
        android:label=&quot;앱이름&quot;
        android:requestLegacyExternalStorage=&quot;true&quot;&amp;gt;
						...&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Development/Flutter</category>
      <author>독행소년</author>
      <guid isPermaLink="true">https://here4you.tistory.com/244</guid>
      <comments>https://here4you.tistory.com/244#entry244comment</comments>
      <pubDate>Fri, 13 Mar 2020 12:54:21 +0900</pubDate>
    </item>
    <item>
      <title>Flutter 강좌 - [Firebase] Cloud Functions 사용법 #3 - 로컬 테스트 | Local Test</title>
      <link>https://here4you.tistory.com/243</link>
      <description>&lt;p style=&quot;font-size: 1.12em; text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em; text-align: justify;&quot;&gt;&lt;b&gt;Flutter Code Examples 강좌를 추천합니다.&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;제 블로그에서 Flutter Code Examples 프로젝트를 시작합니다.&lt;/li&gt;
&lt;li&gt;Flutter의 다양한 예제를 소스코드와 실행화면으로 제공합니다.&lt;/li&gt;
&lt;li&gt;또한 모든 예제는 &lt;a href=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Flutter Code Examples&lt;/a&gt; 앱을 통해 테스트 가능합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;a href=&quot;https://here4you.tistory.com/category/Tutorial/Flutter%20with%20App&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Flutter Code Examples 강좌로 메뉴로 이동&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;a href=&quot;https://here4you.tistory.com/213&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Flutter Code Examples 강좌 목록 페이지로 이동&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;a href=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Flutter Code Examples 앱 설치 | Google Play Store로 이동&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1571794790206&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Flutter Code Examples - Google Play 앱&quot; data-og-description=&quot;Are you a beginner at Flutter? Check out the various features of Flutter through the demo. Source code for all demos is also provided.&quot; data-og-host=&quot;play.google.com&quot; data-og-source-url=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&quot; data-og-url=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&amp;amp;hl=ko&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/czLd1P/hyDnjfJW0L/qDPYD8aFvfFMc98kyKiLK0/img.png?width=512&amp;amp;height=250&amp;amp;face=0_0_512_250,https://scrap.kakaocdn.net/dn/McaI6/hyDnmQ5H8S/TnzOISQwiEcJCDXG9Vbo71/img.png?width=600&amp;amp;height=300&amp;amp;face=0_0_600_300&quot;&gt;&lt;a href=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&amp;amp;hl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/czLd1P/hyDnjfJW0L/qDPYD8aFvfFMc98kyKiLK0/img.png?width=512&amp;amp;height=250&amp;amp;face=0_0_512_250,https://scrap.kakaocdn.net/dn/McaI6/hyDnmQ5H8S/TnzOISQwiEcJCDXG9Vbo71/img.png?width=600&amp;amp;height=300&amp;amp;face=0_0_600_300');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;Flutter Code Examples - Google Play 앱&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;Are you a beginner at Flutter? Check out the various features of Flutter through the demo. Source code for all demos is also provided.&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;play.google.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;hr style=&quot;margin: 20px auto 0px; border: none; cursor: pointer !important; z-index: 1; font-size: 0px; line-height: 0; height: 20px; background: url('../image/divider-line.svg') 0px -180px / 200px 200px no-repeat; width: 200px; color: #333333; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;blockquote class=&quot;tx-quote-tistory&quot;&gt;
&lt;p&gt;Flutter 강좌 시즌2 목록 :&amp;nbsp;&lt;a class=&quot;tx-link&quot; href=&quot;https://here4you.tistory.com/149&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://here4you.tistory.com/149&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;지난 강좌에서 2회에 걸쳐 Cloud Functions의 사용법을 알아봤다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;a href=&quot;https://here4you.tistory.com/228&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2019/11/13 - [Development/Flutter] - Flutter 강좌 - [Firebase] Cloud Functions 사용법 #2&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;a href=&quot;https://here4you.tistory.com/227&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2019/11/12 - [Development/Flutter] - Flutter 강좌 - [Firebase] Cloud Functions 사용법 #1&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;실제로 Cloud Functions을 이용할 때 가장 불편한 점은 함수의 배포(deploy) 시간이 생각보다 오래 걸린다는 것이다. 실제로 1분 이상 걸리기 때문에 함수를 빈번히 수정/배포해가면서 기능을 테스트하기가 굉장히 불편하다. 로컬에서 기능 테스트를 마친 후에 실제 서버에 반영하는 방법이 없을까 하여 알아보니 당연히 있었다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;이번 강좌에서는 Cloud Functions을 로컬에서 테스트 하는 방법에 대해서 알아본다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;1. 관리자 인증 정보 설정&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;로컬에서 테스트할 함수가 Firebase에서 제공하는 여러 서비스를 이용하기를 원한다면 관리자 인증 정보 설정이 필요하다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;구글 클라우드 콘솔에 로그인 한 후 IAM 및 관리자의 서비스 계정 항목으로 이동하자. App Engine default service account 항목의 작업을 클릭해서 키 만들기를 선택한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;이미지 2.png&quot; data-origin-width=&quot;1408&quot; data-origin-height=&quot;609&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GGC4P/btqCFxUOJ8Q/qp03KOG4731yc4EkKasbSk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GGC4P/btqCFxUOJ8Q/qp03KOG4731yc4EkKasbSk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GGC4P/btqCFxUOJ8Q/qp03KOG4731yc4EkKasbSk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGGC4P%2FbtqCFxUOJ8Q%2Fqp03KOG4731yc4EkKasbSk%2Fimg.png&quot; data-filename=&quot;이미지 2.png&quot; data-origin-width=&quot;1408&quot; data-origin-height=&quot;609&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;키 유형을 JSON으로 선택하면 비공개 키가 JSON 파일로 다운로드된다. 이 파일을 통해 Firebase의 클라우드 리소스에 액세스 할 수 있게 되는 것이다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;이 파일을 미리 설치했던 로컬의 Firebase 프로젝트로 복사한 후 파일 이름을 key.json으로 변경한다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;* Firebase CLI 설치 방법은 &lt;a href=&quot;https://here4you.tistory.com/227&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Cloud Functions 사용법 #1&lt;/a&gt;을 참고한다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;2. Firebse 에뮬레이터 실행&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;key.json 파일의 경로를 파라미터로 해서 GOGGLE_APPLICATION_CREDENTIALS 변수를 설정한다. json 파일의 절대 경로로 입력해야 한다. 윈도우의 경우 set 명령어를 이용하며 UNIX의 경우는 set 대신 export로 설정한다.&lt;/p&gt;
&lt;pre id=&quot;code_1583988096175&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;D:\workspace\Firebase\sbt
&amp;lambda; set GOOGLE_APPLICATION_CREDENTIALS=D:\workspace\Firebase\sbt\key.json&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;에뮬레이터를 실행하려면 다음과 같은 명령어를 이용한다.&lt;/p&gt;
&lt;pre id=&quot;code_1583988812915&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$&amp;gt; firebase emulators:start&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;Cloud Functions만 에뮬레이션하고자 한다면 --only 플래그를 이용한다.&lt;/p&gt;
&lt;pre id=&quot;code_1583988854497&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;firebase emulators:start --only functions&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;본인의 실행화면은 다음과 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;이미지 4.png&quot; data-origin-width=&quot;1090&quot; data-origin-height=&quot;376&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kWr2x/btqCF6o7Kwh/JPPBcmSkJwtrTu2GSjpbHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kWr2x/btqCF6o7Kwh/JPPBcmSkJwtrTu2GSjpbHK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kWr2x/btqCF6o7Kwh/JPPBcmSkJwtrTu2GSjpbHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkWr2x%2FbtqCF6o7Kwh%2FJPPBcmSkJwtrTu2GSjpbHK%2Fimg.png&quot; data-filename=&quot;이미지 4.png&quot; data-origin-width=&quot;1090&quot; data-origin-height=&quot;376&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;잠깐 살펴보면, 에뮬레이터 허브라는 것이 로컬의 4400 포트에서 실행되었고, Cloud Functions 에뮬레이터는 로컬의 5001 포트에서 실행되고 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;한 가지 주목할 점은, 본인은 --only functions란 플래그를 이용해서 오직 Cloud Functions의 에뮬레이터만 실행시켰다. 그런데 본인이 개발한 7개의 함수 중 하위 4개는 Firestore를 참조하고 있는 함수들인데, Firestore 에뮬레이터는 실행하지 않았으므로 이들 함수들이 호출되면 실제 서버의 Firestore에 영향이 미치게 된다는 뜻이다. 각자 테스트 목적에 따라 Firestore도 로컬에서 에뮬레이션 할 필요가 있을 경우 Firestore의 에뮬레이터도 실행해야 서버에 영향 없이 테스트를 할 수 있을 것이다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;그리고 상단의 3개의 함수들은 로컬주소의 5001 포트를 통해 호출될 수 있는데 호출에 사용될 URL들이 출력되고 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;3. 함수 테스트&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;자 그럼 functions 폴더의 index.js 파일에 테스트용 함수를 추가해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1583989658331&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;exports.helloWorld = functions.https.onRequest((request, response) =&amp;gt; {
  response.send(&quot;Hello from Firebase!&quot;);
});&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;함수 추가 후 index.js 파일을 저장하면 에뮬레이터는 파일의 변화를 자동으로 감지하고 해당 함수를 자동으로 초기한다.&lt;/p&gt;
&lt;pre id=&quot;code_1583989750552&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;functions[helloWorld]: http function initialized (http://localhost:5001/shareble-baby-tracker/us-central1/helloWorld).&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;웹 브라우저에서 해당 URL을 요청해보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;이미지 5.png&quot; data-origin-width=&quot;502&quot; data-origin-height=&quot;231&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cjC2mS/btqCDjJIrKR/8HqJqSCeK54KsEQVlI45zk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cjC2mS/btqCDjJIrKR/8HqJqSCeK54KsEQVlI45zk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cjC2mS/btqCDjJIrKR/8HqJqSCeK54KsEQVlI45zk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcjC2mS%2FbtqCDjJIrKR%2F8HqJqSCeK54KsEQVlI45zk%2Fimg.png&quot; data-filename=&quot;이미지 5.png&quot; data-origin-width=&quot;502&quot; data-origin-height=&quot;231&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;함수가 정상적으로 호출되는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;이번 강좌에서는 Cloud Functions을 로컬에서 테스트하는 방법에 대해서 알아봤다. 이 방법을 이용하면 로컬에서 함수를 개발하면서 바로바로 기능을 테스트할 수 있고, 테스트가 완료된 후에 배포를 할 수 있기 때문에 개발과 기능 테스트를 위해 빈번히 서버를 멈춰가며 배포하는 부담을 배재할 수 있다.&lt;/p&gt;</description>
      <category>Development/Flutter</category>
      <author>독행소년</author>
      <guid isPermaLink="true">https://here4you.tistory.com/243</guid>
      <comments>https://here4you.tistory.com/243#entry243comment</comments>
      <pubDate>Thu, 12 Mar 2020 14:14:31 +0900</pubDate>
    </item>
    <item>
      <title>부모가 함께 사용할 수 있는 수유 관리 앱을 소개합니다. | 공유형 수유관리 앱</title>
      <link>https://here4you.tistory.com/242</link>
      <description>&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;구글 플레이 스토어에서 수유관리앱을 검색하면 수많은 앱들이 검색됩니다.&lt;/p&gt;
&lt;p&gt;그런데 아이의 활동 기록을 보호자간에 공유할 수 있는 쓸만한 앱을 찾기는 쉽지 않습니다.&lt;/p&gt;
&lt;p&gt;그래서 제가 직접 &lt;span style=&quot;color: #0593d3;&quot;&gt;&lt;b&gt;iNOTE&lt;/b&gt;&lt;/span&gt;를 개발했습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;실시간 공유형 수유관리 앱&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;어려 보호자가 함께 아이의 활동을 기록할 수 있으며, 그 기록은 네트워크를 통해 실시간으로 공유됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;그림16.png&quot; data-origin-width=&quot;512&quot; data-origin-height=&quot;990&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mu4fd/btqCDkuKcsI/ctCzbLDkMPVwllvOZv7LB1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mu4fd/btqCDkuKcsI/ctCzbLDkMPVwllvOZv7LB1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mu4fd/btqCDkuKcsI/ctCzbLDkMPVwllvOZv7LB1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fmu4fd%2FbtqCDkuKcsI%2FctCzbLDkMPVwllvOZv7LB1%2Fimg.png&quot; data-filename=&quot;그림16.png&quot; data-origin-width=&quot;512&quot; data-origin-height=&quot;990&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;클라우드 메시징 서비스&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;보호자 중 한명이 아이의 활동을 기록하면, 다른 가족원들에게 메시지가 전달됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;그림17.png&quot; data-origin-width=&quot;510&quot; data-origin-height=&quot;983&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pRdof/btqCD81QS45/UVA1ueOmxrZ0Lc1TKvKw7k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pRdof/btqCD81QS45/UVA1ueOmxrZ0Lc1TKvKw7k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pRdof/btqCD81QS45/UVA1ueOmxrZ0Lc1TKvKw7k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpRdof%2FbtqCD81QS45%2FUVA1ueOmxrZ0Lc1TKvKw7k%2Fimg.png&quot; data-filename=&quot;그림17.png&quot; data-origin-width=&quot;510&quot; data-origin-height=&quot;983&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;가용 자원&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;얼마나 많은 아이를 등록할 수 있나요?&lt;/p&gt;
&lt;p&gt;얼마나 많은 보호자를 초대할 수 있나요?&lt;/p&gt;
&lt;p&gt;무제한입니다!!!&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;다운로드&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;현재 아이폰용 앱이 출시되었으며, 아이폰용 앱도 출시 예정입니다.&lt;/p&gt;
&lt;p&gt;다운로드 링크:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://play.google.com/store/apps/details?id=com.h4u.shareable_baby_tracker&quot;&gt;https://play.google.com/store/apps/details?id=com.h4u.shareable_baby_tracker&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1583909222975&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;website&quot; data-og-title=&quot;아이노트 iNOTE - 공유형 수유관리앱 - Google Play 앱&quot; data-og-description=&quot;아이노트(iNOTE)는 실시간 공유형 수유관리 앱입니다. - 13종의 활동을 추적 관리할 수 있습니다. &amp;gt; 모유수유, 분유수유, 이유식, 기저기, 수면, 목욕, 약, 병원진료, 예방접종, 체온, 체중, 키, 메모 - 여러 명의 아이들 등록할 수 있습니다. - 아이를 함께 관리할 가족원을 초대할 수 있습니다. - 스마트폰을 교체하더라도 백업/복구 과정 없이 계속 추적 관리할 수 있습니다.&quot; data-og-host=&quot;play.google.com&quot; data-og-source-url=&quot;https://play.google.com/store/apps/details?id=com.h4u.shareable_baby_tracker&quot; data-og-url=&quot;https://play.google.com/store/apps/details?id=com.h4u.shareable_baby_tracker&amp;amp;hl=ko&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/2ol6I/hyFeM8ZqKa/M3H3VkH5G2YK8ICPjWfk7k/img.png?width=512&amp;amp;height=250&amp;amp;face=0_0_512_250,https://scrap.kakaocdn.net/dn/bqwCTp/hyFeRboY8n/iPP7m7Mk9pnmk10o2CtG8K/img.png?width=600&amp;amp;height=300&amp;amp;face=0_0_600_300&quot;&gt;&lt;a href=&quot;https://play.google.com/store/apps/details?id=com.h4u.shareable_baby_tracker&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://play.google.com/store/apps/details?id=com.h4u.shareable_baby_tracker&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/2ol6I/hyFeM8ZqKa/M3H3VkH5G2YK8ICPjWfk7k/img.png?width=512&amp;amp;height=250&amp;amp;face=0_0_512_250,https://scrap.kakaocdn.net/dn/bqwCTp/hyFeRboY8n/iPP7m7Mk9pnmk10o2CtG8K/img.png?width=600&amp;amp;height=300&amp;amp;face=0_0_600_300');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;아이노트 iNOTE - 공유형 수유관리앱 - Google Play 앱&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;아이노트(iNOTE)는 실시간 공유형 수유관리 앱입니다. - 13종의 활동을 추적 관리할 수 있습니다. &amp;gt; 모유수유, 분유수유, 이유식, 기저기, 수면, 목욕, 약, 병원진료, 예방접종, 체온, 체중, 키, 메모 - 여러 명의 아이들 등록할 수 있습니다. - 아이를 함께 관리할 가족원을 초대할 수 있습니다. - 스마트폰을 교체하더라도 백업/복구 과정 없이 계속 추적 관리할 수 있습니다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;play.google.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Development</category>
      <author>독행소년</author>
      <guid isPermaLink="true">https://here4you.tistory.com/242</guid>
      <comments>https://here4you.tistory.com/242#entry242comment</comments>
      <pubDate>Wed, 11 Mar 2020 15:47:45 +0900</pubDate>
    </item>
    <item>
      <title>Looking for a shareable baby tracker app?</title>
      <link>https://here4you.tistory.com/241</link>
      <description>&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Many baby tracking apps are listed on the Google Play Store.&lt;/p&gt;
&lt;p&gt;However, there was no useful app to share your babies' activity history among users(protectors).&lt;/p&gt;
&lt;p&gt;So I developed &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;iNOTE&lt;/b&gt;&lt;/span&gt; myself.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Real-Time shareable baby tracking&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;Multiple protectors can record a baby's activity, which is synchronized in real time over the network.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;그림10.png&quot; data-origin-width=&quot;512&quot; data-origin-height=&quot;990&quot; width=&quot;500&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dp8i5W/btqCB8OHm6c/L49fW1Bz4cScESoJ0agrq0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dp8i5W/btqCB8OHm6c/L49fW1Bz4cScESoJ0agrq0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dp8i5W/btqCB8OHm6c/L49fW1Bz4cScESoJ0agrq0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdp8i5W%2FbtqCB8OHm6c%2FL49fW1Bz4cScESoJ0agrq0%2Fimg.png&quot; data-filename=&quot;그림10.png&quot; data-origin-width=&quot;512&quot; data-origin-height=&quot;990&quot; width=&quot;500&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Cloud&amp;nbsp;messaging&amp;nbsp;service&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;span&gt;When one member of the family records the activity, a message is sent to the other family members.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;그림11.png&quot; data-origin-width=&quot;510&quot; data-origin-height=&quot;983&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dv5cgI/btqCEPnlKcL/W6okK07kooEGtsXbbjv95K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dv5cgI/btqCEPnlKcL/W6okK07kooEGtsXbbjv95K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dv5cgI/btqCEPnlKcL/W6okK07kooEGtsXbbjv95K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdv5cgI%2FbtqCEPnlKcL%2FW6okK07kooEGtsXbbjv95K%2Fimg.png&quot; data-filename=&quot;그림11.png&quot; data-origin-width=&quot;510&quot; data-origin-height=&quot;983&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;b&gt;Available&amp;nbsp;Resources&lt;/b&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;How many babies can I register?&lt;/p&gt;
&lt;p&gt;How&amp;nbsp;many&amp;nbsp;family&amp;nbsp;members&amp;nbsp;can&amp;nbsp;I&amp;nbsp;invite?&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot;&gt;&lt;b&gt;Unlimited!!!&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Download&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;The Android app has been released, and the iPhone app has also been released.&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;An&amp;nbsp;app&amp;nbsp;for&amp;nbsp;Android&amp;nbsp;has&amp;nbsp;been&amp;nbsp;released,&amp;nbsp;and&amp;nbsp;an&amp;nbsp;app&amp;nbsp;for&amp;nbsp;iPhone&amp;nbsp;is&amp;nbsp;coming&amp;nbsp;soon.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Download link:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://play.google.com/store/apps/details?id=com.h4u.shareable_baby_tracker&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://play.google.com/store/apps/details?id=com.h4u.shareable_baby_tracker&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1583906459677&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;website&quot; data-og-title=&quot;아이노트 iNOTE - 공유형 수유관리앱 - Google Play 앱&quot; data-og-description=&quot;아이노트(iNOTE)는 실시간 공유형 수유관리 앱입니다. - 13종의 활동을 추적 관리할 수 있습니다. &amp;gt; 모유수유, 분유수유, 이유식, 기저기, 수면, 목욕, 약, 병원진료, 예방접종, 체온, 체중, 키, 메모 - 여러 명의 아이들 등록할 수 있습니다. - 아이를 함께 관리할 가족원을 초대할 수 있습니다. - 스마트폰을 교체하더라도 백업/복구 과정 없이 계속 추적 관리할 수 있습니다.&quot; data-og-host=&quot;play.google.com&quot; data-og-source-url=&quot;https://play.google.com/store/apps/details?id=com.h4u.shareable_baby_tracker&quot; data-og-url=&quot;https://play.google.com/store/apps/details?id=com.h4u.shareable_baby_tracker&amp;amp;hl=ko&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/2ol6I/hyFeM8ZqKa/M3H3VkH5G2YK8ICPjWfk7k/img.png?width=512&amp;amp;height=250&amp;amp;face=0_0_512_250,https://scrap.kakaocdn.net/dn/bqwCTp/hyFeRboY8n/iPP7m7Mk9pnmk10o2CtG8K/img.png?width=600&amp;amp;height=300&amp;amp;face=0_0_600_300&quot;&gt;&lt;a href=&quot;https://play.google.com/store/apps/details?id=com.h4u.shareable_baby_tracker&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://play.google.com/store/apps/details?id=com.h4u.shareable_baby_tracker&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/2ol6I/hyFeM8ZqKa/M3H3VkH5G2YK8ICPjWfk7k/img.png?width=512&amp;amp;height=250&amp;amp;face=0_0_512_250,https://scrap.kakaocdn.net/dn/bqwCTp/hyFeRboY8n/iPP7m7Mk9pnmk10o2CtG8K/img.png?width=600&amp;amp;height=300&amp;amp;face=0_0_600_300');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;아이노트 iNOTE - 공유형 수유관리앱 - Google Play 앱&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;아이노트(iNOTE)는 실시간 공유형 수유관리 앱입니다. - 13종의 활동을 추적 관리할 수 있습니다. &amp;gt; 모유수유, 분유수유, 이유식, 기저기, 수면, 목욕, 약, 병원진료, 예방접종, 체온, 체중, 키, 메모 - 여러 명의 아이들 등록할 수 있습니다. - 아이를 함께 관리할 가족원을 초대할 수 있습니다. - 스마트폰을 교체하더라도 백업/복구 과정 없이 계속 추적 관리할 수 있습니다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;play.google.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Development</category>
      <author>독행소년</author>
      <guid isPermaLink="true">https://here4you.tistory.com/241</guid>
      <comments>https://here4you.tistory.com/241#entry241comment</comments>
      <pubDate>Wed, 11 Mar 2020 15:37:43 +0900</pubDate>
    </item>
    <item>
      <title>Flutter 강좌 - [Firebase] 출시한 앱이 Firebase에 연동되지 않을 때 통합하는 법 | How to integrate your published app with firebase</title>
      <link>https://here4you.tistory.com/240</link>
      <description>&lt;p style=&quot;font-size: 1.12em; text-align: justify;&quot;&gt;&lt;b&gt;Flutter Code Examples 강좌를 추천합니다.&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;제 블로그에서 Flutter Code Examples 프로젝트를 시작합니다.&lt;/li&gt;
&lt;li&gt;Flutter의 다양한 예제를 소스코드와 실행화면으로 제공합니다.&lt;/li&gt;
&lt;li&gt;또한 모든 예제는 &lt;a href=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Flutter Code Examples&lt;/a&gt; 앱을 통해 테스트 가능합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&quot;https://here4you.tistory.com/category/Tutorial/Flutter%20with%20App&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Flutter Code Examples 강좌로 메뉴로 이동&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://here4you.tistory.com/213&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Flutter Code Examples 강좌 목록 페이지로 이동&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Flutter Code Examples 앱 설치 | Google Play Store로 이동&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1571794790206&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Flutter Code Examples - Google Play 앱&quot; data-og-description=&quot;Are you a beginner at Flutter? Check out the various features of Flutter through the demo. Source code for all demos is also provided.&quot; data-og-host=&quot;play.google.com&quot; data-og-source-url=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&quot; data-og-url=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&amp;amp;hl=ko&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/czLd1P/hyDnjfJW0L/qDPYD8aFvfFMc98kyKiLK0/img.png?width=512&amp;amp;height=250&amp;amp;face=0_0_512_250,https://scrap.kakaocdn.net/dn/McaI6/hyDnmQ5H8S/TnzOISQwiEcJCDXG9Vbo71/img.png?width=600&amp;amp;height=300&amp;amp;face=0_0_600_300&quot;&gt;&lt;a href=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&amp;amp;hl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/czLd1P/hyDnjfJW0L/qDPYD8aFvfFMc98kyKiLK0/img.png?width=512&amp;amp;height=250&amp;amp;face=0_0_512_250,https://scrap.kakaocdn.net/dn/McaI6/hyDnmQ5H8S/TnzOISQwiEcJCDXG9Vbo71/img.png?width=600&amp;amp;height=300&amp;amp;face=0_0_600_300');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;Flutter Code Examples - Google Play 앱&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;Are you a beginner at Flutter? Check out the various features of Flutter through the demo. Source code for all demos is also provided.&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;play.google.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;hr style=&quot;margin: 20px auto 0px; border: none; cursor: pointer !important; z-index: 1; font-size: 0px; line-height: 0; height: 20px; background: url('../image/divider-line.svg') 0px -180px / 200px 200px no-repeat; width: 200px; color: #333333; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;blockquote class=&quot;tx-quote-tistory&quot;&gt;
&lt;p&gt;Flutter 강좌 시즌2 목록 :&amp;nbsp;&lt;a class=&quot;tx-link&quot; href=&quot;https://here4you.tistory.com/149&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://here4you.tistory.com/149&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;최근 4개월에 걸쳐 Firebase 기반의 Flutter 앱을 개발했고 지난 목요일에 플레이 스토어에 업로드를 했다. 그리고 조금 전 심사가 끝나고 출시가 되었다.&lt;/p&gt;
&lt;p&gt;그런데.. 그런데 출시된 앱이 Firebase에 연동되지 않는다. 왜 그럴까?&lt;/p&gt;
&lt;p&gt;debug용 SHA 지문을 등록해서 앱을 개발했었고, release용 SHA 지문도 Firebase 프로젝트에 등록해놨었기에 당연히 문제없이 서비스가 가능할 것이라 생각했는데 이상하다.&lt;/p&gt;
&lt;p&gt;일단 Firebase에서 해당 프로젝트의 설정(settings) 메뉴로 진입한 후 통합 메뉴로 들어가 보자&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;여러 서비스 중에서 Google Play를 연결 시도하면 다음과 같은 화면이 나온다. 연결을 마치면 SHA 인증서를 가져온다고 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;이미지 1.png&quot; data-origin-width=&quot;1340&quot; data-origin-height=&quot;1166&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zH8ba/btqCuZ6k0ok/APguKZi40HNT54YXYbio71/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zH8ba/btqCuZ6k0ok/APguKZi40HNT54YXYbio71/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zH8ba/btqCuZ6k0ok/APguKZi40HNT54YXYbio71/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzH8ba%2FbtqCuZ6k0ok%2FAPguKZi40HNT54YXYbio71%2Fimg.png&quot; data-filename=&quot;이미지 1.png&quot; data-origin-width=&quot;1340&quot; data-origin-height=&quot;1166&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;연결 후 일반 메뉴로 이동한 후 앱에 등록된 SHA 인증서 항목을 보면 자동으로 지문이 추가되는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;앱을 재실행해보면 Firebase와도 정상 연결되는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Development/Flutter</category>
      <author>독행소년</author>
      <guid isPermaLink="true">https://here4you.tistory.com/240</guid>
      <comments>https://here4you.tistory.com/240#entry240comment</comments>
      <pubDate>Sun, 8 Mar 2020 13:47:00 +0900</pubDate>
    </item>
    <item>
      <title>Flutter 강좌 - 앱 시작 화면 수정 방법 | 네이티브 스플래쉬 스크린 수정 | Native Splash Screen Customazing</title>
      <link>https://here4you.tistory.com/239</link>
      <description>&lt;p style=&quot;font-size: 1.12em; text-align: justify;&quot;&gt;&lt;b&gt;Flutter Code Examples 강좌를 추천합니다.&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;제 블로그에서 Flutter Code Examples 프로젝트를 시작합니다.&lt;/li&gt;
&lt;li&gt;Flutter의 다양한 예제를 소스코드와 실행화면으로 제공합니다.&lt;/li&gt;
&lt;li&gt;또한 모든 예제는 &lt;a href=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Flutter Code Examples&lt;/a&gt; 앱을 통해 테스트 가능합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;a href=&quot;https://here4you.tistory.com/category/Tutorial/Flutter%20with%20App&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Flutter Code Examples 강좌로 메뉴로 이동&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;a href=&quot;https://here4you.tistory.com/213&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Flutter Code Examples 강좌 목록 페이지로 이동&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;a href=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Flutter Code Examples 앱 설치 | Google Play Store로 이동&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1571794790206&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Flutter Code Examples - Google Play 앱&quot; data-og-description=&quot;Are you a beginner at Flutter? Check out the various features of Flutter through the demo. Source code for all demos is also provided.&quot; data-og-host=&quot;play.google.com&quot; data-og-source-url=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&quot; data-og-url=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&amp;amp;hl=ko&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/czLd1P/hyDnjfJW0L/qDPYD8aFvfFMc98kyKiLK0/img.png?width=512&amp;amp;height=250&amp;amp;face=0_0_512_250,https://scrap.kakaocdn.net/dn/McaI6/hyDnmQ5H8S/TnzOISQwiEcJCDXG9Vbo71/img.png?width=600&amp;amp;height=300&amp;amp;face=0_0_600_300&quot;&gt;&lt;a href=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&amp;amp;hl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/czLd1P/hyDnjfJW0L/qDPYD8aFvfFMc98kyKiLK0/img.png?width=512&amp;amp;height=250&amp;amp;face=0_0_512_250,https://scrap.kakaocdn.net/dn/McaI6/hyDnmQ5H8S/TnzOISQwiEcJCDXG9Vbo71/img.png?width=600&amp;amp;height=300&amp;amp;face=0_0_600_300');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;Flutter Code Examples - Google Play 앱&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;Are you a beginner at Flutter? Check out the various features of Flutter through the demo. Source code for all demos is also provided.&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;play.google.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;hr style=&quot;margin: 20px auto 0px; border: none; cursor: pointer !important; z-index: 1; font-size: 0px; line-height: 0; height: 20px; background: url('../image/divider-line.svg') 0px -180px / 200px 200px no-repeat; width: 200px; color: #333333; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;blockquote class=&quot;tx-quote-tistory&quot;&gt;
&lt;p&gt;Flutter 강좌 시즌2 목록 :&amp;nbsp;&lt;a class=&quot;tx-link&quot; href=&quot;https://here4you.tistory.com/149&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://here4you.tistory.com/149&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;Flutter 앱을 개발해서 실행을 해보면 실제로 개발한 첫번째 페이지(스크린)이 생성되어 출력되기 전에 약 1초 정도 하얀색 화면이 먼저 출력된다. 첫번째 인트로 페이지를 아무리 멋지게 구성해도 인트로 페이지가 출력되지 전에 하얀색 스크린이 먼저 출력되니 앱의 품격(?)이 떨어지게 된다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;우선 앱 실행 초기에 잠시 출력되는 스크린을 네이티브 스플래쉬(Native Splash) 스크린이라고 한다. Flutter 앱은 멀티 플랫폼을 지원하는 크로스 플랫폼 앱이다. 하지만 실제 안드로이드나 iOS의 입장에서 보면 각 플랫폼의 네이티브 앱이 먼저 실행된 후 Flutter 앱으로 점프하는 방식이고 개발한 Flutter 앱이 실행되기 전에 각 플랫폼의 네이티브 스크린이 잠식 출력되게 되면서 발생하는 문제다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;해당 현상은 다음과 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;anigif.gif&quot; data-origin-width=&quot;389&quot; data-origin-height=&quot;800&quot; width=&quot;300&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/muPV9/btqBXGjJXYm/ODXDuPUkyFRsyr3kQy8pUK/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/muPV9/btqBXGjJXYm/ODXDuPUkyFRsyr3kQy8pUK/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/muPV9/btqBXGjJXYm/ODXDuPUkyFRsyr3kQy8pUK/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/muPV9/btqBXGjJXYm/ODXDuPUkyFRsyr3kQy8pUK/img.gif&quot; data-filename=&quot;anigif.gif&quot; data-origin-width=&quot;389&quot; data-origin-height=&quot;800&quot; width=&quot;300&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;바탕화면에서 Flutter 앱의 화면이 출력되는 사이에 하얀색 페이지가 잠시 나타나는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;해결방법은 기본 네이티브 스플래쉬 스크린을 앱의 스타일에 맞게 커스터마이징 하는 것이다. 이미 이를 지원하는 플러그인이 존재한다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;b&gt;* Plug-In&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;a href=&quot;https://pub.dev/packages/flutter_native_splash&quot;&gt;https://pub.dev/packages/flutter_native_splash&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1581477712029&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;website&quot; data-og-title=&quot;flutter_native_splash | Dart Package&quot; data-og-description=&quot;Automatically generates native code for adding splash screens in Android and iOS. Customize with specific platform, background color and splash image.&quot; data-og-host=&quot;pub.dev&quot; data-og-source-url=&quot;https://pub.dev/packages/flutter_native_splash&quot; data-og-url=&quot;https://pub.dev/packages/flutter_native_splash&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/otCZP/hyEUfcmgjy/EsVqkYPW84wJFzApHJfwdK/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400&quot;&gt;&lt;a href=&quot;https://pub.dev/packages/flutter_native_splash&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://pub.dev/packages/flutter_native_splash&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/otCZP/hyEUfcmgjy/EsVqkYPW84wJFzApHJfwdK/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;flutter_native_splash | Dart Package&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;Automatically generates native code for adding splash screens in Android and iOS. Customize with specific platform, background color and splash image.&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;pub.dev&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;플러그인 설치를 위해 pubspec.yaml 파일의 dev_dependencies 항목에 플러그인을 추가한다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1581477967271&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dev_dependencies:
  flutter_native_splash: ^0.1.9&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;그리고 pubspec.yaml 파일 하단에 네이티브 스플래쉬 스크린의 배경색을 설정하는 내용을 추가한다.&lt;/p&gt;
&lt;pre id=&quot;code_1581478115738&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;flutter_native_splash:
  color: &quot;42a5f5&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;설정을 마쳤으며 터미널에서 다음의 명령어를 실행한다.&lt;/p&gt;
&lt;pre id=&quot;code_1581478368089&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;gt; flutter pub pub run flutter_native_splash:create&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;명령어를 실행하면 다음과 같이 안드로이드와 iOS의 네이티브 스플래쉬 스크린이 업데이트되는 것을 확인할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1581478435207&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;D:\workspace\Flutter\mywork\native_splash_test&amp;gt;flutter pub pub run flutter_native_splash:create
[Android] Updating colors.xml with color for splash screen background
[Android] Updating styles.xml with full screen mode setting
[iOS] Updating LaunchScreen.storyboard with width, height and color
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;앱을 실행해보면 다음과 같이 네이티브 스플래쉬 스크린의 배경색이 변경되는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;anigif.gif&quot; data-origin-width=&quot;389&quot; data-origin-height=&quot;800&quot; width=&quot;300&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cltkfV/btqBVLzzW1T/yankxViokkTdDJ84QDvd8k/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cltkfV/btqBVLzzW1T/yankxViokkTdDJ84QDvd8k/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cltkfV/btqBVLzzW1T/yankxViokkTdDJ84QDvd8k/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/cltkfV/btqBVLzzW1T/yankxViokkTdDJ84QDvd8k/img.gif&quot; data-filename=&quot;anigif.gif&quot; data-origin-width=&quot;389&quot; data-origin-height=&quot;800&quot; width=&quot;300&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;배경색이 앱의 테마와 같은 색으로 처리되니 한결 매끄럽게 실행되는 것처럼 느껴진다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;네이티브 스크린에 이미지를 추가할 수도 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;프로젝트 root에 assets 폴더를 생성하고 이미지 파일을 추가한 후 pubspec.yaml 파일의 설정에 이미지의 위치를 추가한다.&lt;/p&gt;
&lt;pre id=&quot;code_1581478928504&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;flutter_native_splash:
  image: assets/ic_launcher.png
  color: &quot;42a5f5&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;다시 네이티브 스크린을 업데이트 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1581478979782&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;D:\workspace\Flutter\mywork\native_splash_test&amp;gt;flutter pub pub run flutter_native_splash:create
[Android] Creating splash images
[Android] Updating launch_background.xml with splash image path
[Android] Updating colors.xml with color for splash screen background
[Android] Updating styles.xml with full screen mode setting
[iOS] Creating splash images
[iOS] Updating LaunchScreen.storyboard with width, height and color&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;이번에는 이미지 파일이 추가로 업데이트 되는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;앱을 실행해 보자.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;본인의 경우에는 이미지가 출력되지 않고 기존의 배경색만 수정된 상태로 변화없이 출력이 되었다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;뭐 안드로이드 스튜디오나 플러그인이 내 맘대로 동작 안해준 것이 한 두 번도 아닌데.. 슬슬 원인을 찾아 해결해 보자.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;본인의 경우 이미지 파일로 Flutter 의 기본 런처 이미지인 ic_launcher.png를 사용했다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;우선 네이티브 코드가 있는 android &amp;gt; src &amp;gt; main &amp;gt; res 폴더로 이동해 보자&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;이미지 2.png&quot; data-origin-width=&quot;343&quot; data-origin-height=&quot;663&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ccczey/btqBW0v5d6g/i8IyWkRM8nyBoWBFVKGY0k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ccczey/btqBW0v5d6g/i8IyWkRM8nyBoWBFVKGY0k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ccczey/btqBW0v5d6g/i8IyWkRM8nyBoWBFVKGY0k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fccczey%2FbtqBW0v5d6g%2Fi8IyWkRM8nyBoWBFVKGY0k%2Fimg.png&quot; data-filename=&quot;이미지 2.png&quot; data-origin-width=&quot;343&quot; data-origin-height=&quot;663&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;assets 폴더에 넣었던 ic_launcher.png 파일이 네이티브의 res &amp;gt; drawable의 각 해상도 폴더로 splash.png 파일로 이름이 변경되어 이동된 것을 확인할 수 있다. 실제 파일을 눌러보면 ic_launcher.png과 동일한 이미지가 출력된다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;다음으로 drawable 폴더의 launch_background.xml 파일을 보자.&lt;/p&gt;
&lt;pre id=&quot;code_1581481506058&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&amp;gt;
&amp;lt;!-- Modify this file to customize your launch splash screen --&amp;gt;
&amp;lt;layer-list xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;&amp;gt;
    &amp;lt;item android:drawable=&quot;@color/splash_color&quot; /&amp;gt;

    &amp;lt;!-- You can insert your own image assets here --&amp;gt;
    &amp;lt;!-- &amp;lt;item&amp;gt;
        &amp;lt;bitmap
            android:gravity=&quot;center&quot;
            android:src=&quot;@mipmap/launch_image&quot; /&amp;gt;
    &amp;lt;/item&amp;gt; --&amp;gt;

&amp;lt;/layer-list&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;첫번째 아이템으로 splash_color 항목이 있는데 정상 반영되었으니 배경색이 변경되었을 것이다. 문제는 이미지를 출력하는 코드가 존재하지 않는다. 플러그인 사이트의 git repository를 둘러보니 두번째 아이템으로 이미지가 추가되어야 한다. 코드를 다음과 같이 변경하자.&lt;/p&gt;
&lt;pre id=&quot;code_1581481735611&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&amp;gt;
&amp;lt;!-- Modify this file to customize your launch splash screen --&amp;gt;
&amp;lt;layer-list xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;&amp;gt;
    &amp;lt;item android:drawable=&quot;@color/splash_color&quot; /&amp;gt;

    &amp;lt;item&amp;gt;
        &amp;lt;bitmap android:gravity=&quot;center&quot; android:src=&quot;@drawable/splash&quot; /&amp;gt;
    &amp;lt;/item&amp;gt;

&amp;lt;/layer-list&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;두번째 아이템으로 bitmap 이미지를 추가하면서 이미지의 경로를 drawable의 splash 파일로 지정한 것이다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;앱을 다시 실행해보면 다음과 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;anigif.gif&quot; data-origin-width=&quot;389&quot; data-origin-height=&quot;800&quot; width=&quot;300&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bskru6/btqBVLsTU7a/B0QMhwff9PYgheKVSx5MI1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bskru6/btqBVLsTU7a/B0QMhwff9PYgheKVSx5MI1/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bskru6/btqBVLsTU7a/B0QMhwff9PYgheKVSx5MI1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bskru6/btqBVLsTU7a/B0QMhwff9PYgheKVSx5MI1/img.gif&quot; data-filename=&quot;anigif.gif&quot; data-origin-width=&quot;389&quot; data-origin-height=&quot;800&quot; width=&quot;300&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;이미지가 출력되지 시작했다. 그런데 이미지의 위치가 바뀌면서 흔들리는 느낌이 난다. 이것은 네이티브 스크린이 풀스크린(Fullscreen)으로 변경되면서 발생하는 것이다. 이를 해결하기 위해서는 values 폴더의 styles.xml 파일의 windowFullscreen 항목을 false로 변경한다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;앱을 다시 실행해보면 다음과 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;anigif.gif&quot; data-origin-width=&quot;389&quot; data-origin-height=&quot;800&quot; width=&quot;300&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cYA6DA/btqBUTZfFZs/xcwQaY6Nhefe4YBIQfoeZ0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cYA6DA/btqBUTZfFZs/xcwQaY6Nhefe4YBIQfoeZ0/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cYA6DA/btqBUTZfFZs/xcwQaY6Nhefe4YBIQfoeZ0/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/cYA6DA/btqBUTZfFZs/xcwQaY6Nhefe4YBIQfoeZ0/img.gif&quot; data-filename=&quot;anigif.gif&quot; data-origin-width=&quot;389&quot; data-origin-height=&quot;800&quot; width=&quot;300&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;이미지의 위치 이동없이 정상적으로 네이티브 스크린이 출력되는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Development/Flutter</category>
      <author>독행소년</author>
      <guid isPermaLink="true">https://here4you.tistory.com/239</guid>
      <comments>https://here4you.tistory.com/239#entry239comment</comments>
      <pubDate>Wed, 12 Feb 2020 13:39:06 +0900</pubDate>
    </item>
    <item>
      <title>Flutter 강좌 - [Firebase] 레거시 GAE 및 GCF 메타데이터 서버 엔드포인드 사용 문제 | Legacy GAE and GCF Metadata Server endpoints Problem</title>
      <link>https://here4you.tistory.com/238</link>
      <description>&lt;p style=&quot;font-size: 1.12em; text-align: justify;&quot;&gt;&lt;b&gt;Flutter Code Examples 강좌를 추천합니다.&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;제 블로그에서 Flutter Code Examples 프로젝트를 시작합니다.&lt;/li&gt;
&lt;li&gt;Flutter의 다양한 예제를 소스코드와 실행화면으로 제공합니다.&lt;/li&gt;
&lt;li&gt;또한 모든 예제는 &lt;a href=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Flutter Code Examples&lt;/a&gt; 앱을 통해 테스트 가능합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;a href=&quot;https://here4you.tistory.com/category/Tutorial/Flutter%20with%20App&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Flutter Code Examples 강좌로 메뉴로 이동&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;a href=&quot;https://here4you.tistory.com/213&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Flutter Code Examples 강좌 목록 페이지로 이동&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;a href=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Flutter Code Examples 앱 설치 | Google Play Store로 이동&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1571794790206&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Flutter Code Examples - Google Play 앱&quot; data-og-description=&quot;Are you a beginner at Flutter? Check out the various features of Flutter through the demo. Source code for all demos is also provided.&quot; data-og-host=&quot;play.google.com&quot; data-og-source-url=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&quot; data-og-url=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&amp;amp;hl=ko&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/czLd1P/hyDnjfJW0L/qDPYD8aFvfFMc98kyKiLK0/img.png?width=512&amp;amp;height=250&amp;amp;face=0_0_512_250,https://scrap.kakaocdn.net/dn/McaI6/hyDnmQ5H8S/TnzOISQwiEcJCDXG9Vbo71/img.png?width=600&amp;amp;height=300&amp;amp;face=0_0_600_300&quot;&gt;&lt;a href=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&amp;amp;hl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/czLd1P/hyDnjfJW0L/qDPYD8aFvfFMc98kyKiLK0/img.png?width=512&amp;amp;height=250&amp;amp;face=0_0_512_250,https://scrap.kakaocdn.net/dn/McaI6/hyDnmQ5H8S/TnzOISQwiEcJCDXG9Vbo71/img.png?width=600&amp;amp;height=300&amp;amp;face=0_0_600_300');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;Flutter Code Examples - Google Play 앱&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;Are you a beginner at Flutter? Check out the various features of Flutter through the demo. Source code for all demos is also provided.&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;play.google.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;hr style=&quot;margin: 20px auto 0px; border: none; cursor: pointer !important; z-index: 1; font-size: 0px; line-height: 0; height: 20px; background: url('../image/divider-line.svg') 0px -180px / 200px 200px no-repeat; width: 200px; color: #333333; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;blockquote class=&quot;tx-quote-tistory&quot;&gt;
&lt;p&gt;Flutter 강좌 시즌2 목록 :&amp;nbsp;&lt;a class=&quot;tx-link&quot; href=&quot;https://here4you.tistory.com/149&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://here4you.tistory.com/149&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;최근 Google Cloud Platform으로부터 경고 메일을 받았다. 영문 메일을 먼저 받았고, 약 보름만에 한글로 동일한 메일을 받았다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;제목은 다음과 같이 날라온다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;span&gt;Reminder: [Action Required] Legacy GAE and GCF Metadata Server endpoints will be turned down on April 30, 2020&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;span&gt;&lt;span&gt;알림: [조치 필요] 2020년 4월 30일에 레거시 GAE 및 GCF 메타데이터 서버 엔드포인트의 사용이 중단될 예정입니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;span&gt;&lt;span&gt;메일의 내용은 본인 소유의 App Engine 애플리케이션이나 Cloud Functions 프로젝트에서 App Engine 및 Cloud Functions 메타 데이터 서버의 v0.1 혹은 v1beta1 엔드포인트로 요청을 보내고 있는데 4월 30일 이후에는 정식 v1 엔드포인트가 아닌 URL로 접근하는 요청에 대해서는 HTTP 404 NOT FOUND 응답이 반환되니 기한내에 v1으로 업그레이드를 하라는 말인데 영문도 그렇고 번역된 한글도 그렇고 도무지 이해를 못하겠다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;우선 내 경우에는 3개의 Cloud Function을 Firebase에 등록해서 개발 중인 상황이다. 그런데 정작 메일은 Firebase가 아닌 GCP(Google Cloud Platform)에서 발송되었다. GCP내에 App Engine이나 Cloud Functions을 쓰고 있는데 그 기능에서 v0.1 혹으 v1beta를 사용하고 있어 문제가 발생하는 것이고 이를 정식 v1으로 업그레이드 하라는 뜻인거 같다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;정작 본인은 GCP는 사용 안하고 있는데 하면서 살펴보니 Firebase에 Cloud Functions을 등록하면 GCP에도 등록되는가 보다. 아마 실체는 하나인데 Firebase와 GCP를 통해 접근이 가능한 것 같다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;여튼 내가 만든 Cloud 함수가 아래와 같은 URL을 요청할 경우 v1 형식으로 업그레이드를 해야한다는 뜻인 것 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;576&quot; data-origin-height=&quot;169&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k2ckh/btqBJeCqfd6/Tv5y9sXooRZfBq6jGgn4Z0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k2ckh/btqBJeCqfd6/Tv5y9sXooRZfBq6jGgn4Z0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k2ckh/btqBJeCqfd6/Tv5y9sXooRZfBq6jGgn4Z0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk2ckh%2FbtqBJeCqfd6%2FTv5y9sXooRZfBq6jGgn4Z0%2Fimg.png&quot; data-origin-width=&quot;576&quot; data-origin-height=&quot;169&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;우선 문제 해결을 위해 Stackoverflow를 돌아봐도 동일 문제를 문의한 글은 봤으나 명확한 답변이 없는 상황이다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;그나마 가장 답변다운 답변이 달린 링크는 다음과 같다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/58759895/firebase-sendmessage-function-update-to-v1-google-cloud-endpoint&quot;&gt;https://stackoverflow.com/questions/58759895/firebase-sendmessage-function-update-to-v1-google-cloud-endpoint&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;내용인 즉, firebase CLI 프로젝트의 functions에 설치한 npm 패키지 중에 문제의 URL을 요청하는 패키지를 찾아서 업그래이드를 하면 해결된다는 내용이었다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;방식은 grep 명령어를 통해 문제의 키워드를 검색해서 검색된 패키지를 업그래이드 하는 것이다. 답변자의 경우 firebase-admin에서 문제가 발생해서 관련 패키지들을 업그레이드해서 해결했다고 한다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;본인의 경우는 문제가 되는 함수에서&lt;u&gt;&lt;b&gt;만&lt;/b&gt;&lt;/u&gt; firebase-tools를 사용하고 있었다. 해당 패키지에서 문제의 URL이 나오는지 확인하고 업그래이드를 시도했다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;사용한 grep 명령어는 다음과 같다. firebaes CLI 프로젝트의 functions 디렉토리에서 시작한다.&lt;/p&gt;
&lt;pre id=&quot;code_1580880760950&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;grep -rni &quot;computeMetadata/&quot; *&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;첫번째 시도&lt;/p&gt;
&lt;pre id=&quot;code_1580880797651&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lambda; grep -rni &quot;computeMetadata/&quot; *
node_modules/firebase-admin/lib/auth/credential.js:31:var GOOGLE_METADATA_SERVICE_TOKEN_PATH = '/computeMetadata/v1/instance/service-accounts/default/token';
node_modules/firebase-admin/lib/auth/credential.js:32:var GOOGLE_METADATA_SERVICE_PROJECT_ID_PATH = '/computeMetadata/v1/project/project-id';
node_modules/firebase-admin/lib/auth/token-generator.js:128:            url: 'http://metadata/computeMetadata/v1/instance/service-accounts/default/email',
node_modules/gcp-metadata/build/src/index.d.ts:10:export declare const BASE_PATH = &quot;/computeMetadata/v1&quot;;
node_modules/gcp-metadata/build/src/index.js:12:exports.BASE_PATH = '/computeMetadata/v1';
node_modules/google-auto-auth/node_modules/gcp-metadata/index.js:6:var BASE_URL = 'http://metadata.google.internal/computeMetadata/v1'
node_modules/google-auto-auth/node_modules/google-auth-library/lib/auth/computeclient.js:27:  'http://metadata.google.internal/computeMetadata/v1beta1/instance/service-accounts/default/token';
node_modules/google-auto-auth/node_modules/google-auth-library/lib/auth/googleauth.js:254:    uri: 'http://169.254.169.254/computeMetadata/v1/project/project-id',&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;google-auto-auth 패키지 내 google-auth-library에서 v1beta1이 검색되었다. 일단 firebae-tools는 아니다. ㅡ,ㅡ&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;당시 google-auth-auth는 0.7.2 였고 0.10.1로 업그래이드 했다.&lt;/p&gt;
&lt;pre id=&quot;code_1580880936503&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lambda; npm install google-auto-auth --save&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;두번째 시도&lt;/p&gt;
&lt;pre id=&quot;code_1580880952143&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lambda; grep -rni &quot;computeMetadata/&quot; *
node_modules/firebase-admin/lib/auth/credential.js:31:var GOOGLE_METADATA_SERVICE_TOKEN_PATH = '/computeMetadata/v1/instance/service-accounts/default/token';
node_modules/firebase-admin/lib/auth/credential.js:32:var GOOGLE_METADATA_SERVICE_PROJECT_ID_PATH = '/computeMetadata/v1/project/project-id';
node_modules/firebase-admin/lib/auth/token-generator.js:128:            url: 'http://metadata/computeMetadata/v1/instance/service-accounts/default/email',
node_modules/firebase-tools/node_modules/gcp-metadata/index.js:6:var BASE_URL = 'http://metadata.google.internal/computeMetadata/v1'
node_modules/firebase-tools/node_modules/google-auto-auth/node_modules/google-auth-library/lib/auth/computeclient.js:27:  'http://metadata.google.internal/computeMetadata/v1beta1/instance/service-accounts/default/token';
node_modules/firebase-tools/node_modules/google-auto-auth/node_modules/google-auth-library/lib/auth/googleauth.js:254:    uri: 'http://169.254.169.254/computeMetadata/v1/project/project-id',
node_modules/gcp-metadata/build/src/index.d.ts:10:export declare const BASE_PATH = &quot;/computeMetadata/v1&quot;;
node_modules/gcp-metadata/build/src/index.js:12:exports.BASE_PATH = '/computeMetadata/v1';
node_modules/google-auto-auth/node_modules/gcp-metadata/build/src/index.d.ts:3:export declare const BASE_PATH = &quot;/computeMetadata/v1&quot;;
node_modules/google-auto-auth/node_modules/gcp-metadata/build/src/index.js:42:exports.BASE_PATH = '/computeMetadata/v1';&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;firebase-tools 패키지 내 google-auth-auth 패키지 내 google-auth-library에서 v1beta1이 검색되었다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;google-auto-auth 디렉토리까지 이동한 후 업그레이드를 시도한다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;당시 google-auth-library는 1.6.1이었고 5.9.2로 업그래이드 했다. 그냥 인스톨하면 버전이 오히려 내려가서 @latest 옵션을 추가하여 최신 버전으로 업그래이드 했다.&lt;/p&gt;
&lt;pre id=&quot;code_1580881102914&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lambda; npm install google-auth-library@latest --save&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;세번재 시도&lt;/p&gt;
&lt;pre id=&quot;code_1580881199391&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lambda; grep -rni &quot;computeMetadata/&quot; *
node_modules/firebase-admin/lib/auth/credential.js:31:var GOOGLE_METADATA_SERVICE_TOKEN_PATH = '/computeMetadata/v1/instance/service-accounts/default/token';
node_modules/firebase-admin/lib/auth/credential.js:32:var GOOGLE_METADATA_SERVICE_PROJECT_ID_PATH = '/computeMetadata/v1/project/project-id';
node_modules/firebase-admin/lib/auth/token-generator.js:128:            url: 'http://metadata/computeMetadata/v1/instance/service-accounts/default/email',
node_modules/firebase-tools/node_modules/gcp-metadata/index.js:6:var BASE_URL = 'http://metadata.google.internal/computeMetadata/v1'
node_modules/firebase-tools/node_modules/google-auto-auth/node_modules/google-auth-library/node_modules/gcp-metadata/build/src/index.d.ts:10:export declare const BASE_PATH = &quot;/computeMetadata/v1&quot;;
node_modules/firebase-tools/node_modules/google-auto-auth/node_modules/google-auth-library/node_modules/gcp-metadata/build/src/index.js:12:exports.BASE_PATH = '/computeMetadata/v1';
node_modules/gcp-metadata/build/src/index.d.ts:10:export declare const BASE_PATH = &quot;/computeMetadata/v1&quot;;
node_modules/gcp-metadata/build/src/index.js:12:exports.BASE_PATH = '/computeMetadata/v1';
node_modules/google-auto-auth/node_modules/gcp-metadata/build/src/index.d.ts:3:export declare const BASE_PATH = &quot;/computeMetadata/v1&quot;;
node_modules/google-auto-auth/node_modules/gcp-metadata/build/src/index.js:42:exports.BASE_PATH = '/computeMetadata/v1';&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;v1beta로 검색되는 패키지가 없어졌다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;이 상태에서 firebase deploy 명령어를 통해 함수를 다시 배포한다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;이렇게 해서 해결이 된 것인지는 본인도 모르겠다. 메일을 받은 것 외에는 Firebase나 GCP 콘솔상의 Cloud Functions 메뉴에서는 어떠한 경고문구도 출력되지 않았었다. 이번 조치로 해결되었는지 확인할 방법도 현재까지 찾지 못했다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;이번달 출시할 서비스인데 4월 30일부터 문제가 생기는 것은 아닌지 불안하기만 하다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;[2020년 2월 24일 추가]&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;지난 2월 5일의 조치를 정리하여 포스팅하였는데 지난 2월 22일에 다시 동일한 메일을 받았다. 문제가 발생하는 Cloud Functions도 동일하다. 문제가되는 함수에서 사용하는 구글 관련 라이브러리는 Firebase-tools 였다. 때마침 새로운 버전(7.13.1)이 출시되었길래 일단 업데이트를 한 후 deploy를 했다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;또한 메일의 후반부에 Cloud Functions을 사용한다면 환경 변수를 추가하라는 아내에 따라 다음과 같이 환경변수도 추가했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;이미지 2.png&quot; data-origin-width=&quot;737&quot; data-origin-height=&quot;459&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BcZkN/btqCgob3oiR/PANDN2UY1lLg2m9KC5gK8k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BcZkN/btqCgob3oiR/PANDN2UY1lLg2m9KC5gK8k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BcZkN/btqCgob3oiR/PANDN2UY1lLg2m9KC5gK8k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBcZkN%2FbtqCgob3oiR%2FPANDN2UY1lLg2m9KC5gK8k%2Fimg.png&quot; data-filename=&quot;이미지 2.png&quot; data-origin-width=&quot;737&quot; data-origin-height=&quot;459&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;아직도 현재까지의 조치가 맞는 조치인지와 문제가 해결되었는지의 유무를 알 수 없다. stackoverflow에도 명확한 자료가 없어서 답답하다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Development/Flutter</category>
      <author>독행소년</author>
      <guid isPermaLink="true">https://here4you.tistory.com/238</guid>
      <comments>https://here4you.tistory.com/238#entry238comment</comments>
      <pubDate>Wed, 5 Feb 2020 14:42:48 +0900</pubDate>
    </item>
    <item>
      <title>2020-01-26 | 한밭수목원 열대식물원 | G9X mark2</title>
      <link>https://here4you.tistory.com/237</link>
      <description>&lt;p&gt;대전 한밭수목원내 위치한 열대식물원&lt;/p&gt;
&lt;p&gt;규모는 크지 않지만 아기자기하니 잘 꾸며 놓아서 자주 찾는 곳이다.&lt;/p&gt;
&lt;p&gt;최근에는 별과도 오픈해서 다육이와 열대과수 테마도 즐길 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1333&quot; data-origin-height=&quot;2000&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eCtfso/btqBuxhzDCX/4ddQY9HRYTvCYcVCJkkKy0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eCtfso/btqBuxhzDCX/4ddQY9HRYTvCYcVCJkkKy0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eCtfso/btqBuxhzDCX/4ddQY9HRYTvCYcVCJkkKy0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeCtfso%2FbtqBuxhzDCX%2F4ddQY9HRYTvCYcVCJkkKy0%2Fimg.png&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1333&quot; data-origin-height=&quot;2000&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1333&quot; data-origin-height=&quot;2000&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bRfdfg/btqBtFA3LoW/l4xTJ18GsxIO96icCsIFrK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bRfdfg/btqBtFA3LoW/l4xTJ18GsxIO96icCsIFrK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bRfdfg/btqBtFA3LoW/l4xTJ18GsxIO96icCsIFrK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbRfdfg%2FbtqBtFA3LoW%2Fl4xTJ18GsxIO96icCsIFrK%2Fimg.png&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1333&quot; data-origin-height=&quot;2000&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;20200126163217_IMG_0220.JPG&quot; data-origin-width=&quot;5472&quot; data-origin-height=&quot;3648&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dUvGYL/btqBxAR9aH2/17tMzfBfWfdEW00h2RHHBK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dUvGYL/btqBxAR9aH2/17tMzfBfWfdEW00h2RHHBK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dUvGYL/btqBxAR9aH2/17tMzfBfWfdEW00h2RHHBK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdUvGYL%2FbtqBxAR9aH2%2F17tMzfBfWfdEW00h2RHHBK%2Fimg.jpg&quot; data-filename=&quot;20200126163217_IMG_0220.JPG&quot; data-origin-width=&quot;5472&quot; data-origin-height=&quot;3648&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1333&quot; data-origin-height=&quot;2000&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dditem/btqBxzsabh4/UiutiYRvB4WwNP1PmsUXmK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dditem/btqBxzsabh4/UiutiYRvB4WwNP1PmsUXmK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dditem/btqBxzsabh4/UiutiYRvB4WwNP1PmsUXmK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdditem%2FbtqBxzsabh4%2FUiutiYRvB4WwNP1PmsUXmK%2Fimg.png&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1333&quot; data-origin-height=&quot;2000&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1333&quot; data-origin-height=&quot;2000&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmCawT/btqBt99hLL0/5NoKHSkBGK8GgaVFfWof50/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmCawT/btqBt99hLL0/5NoKHSkBGK8GgaVFfWof50/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmCawT/btqBt99hLL0/5NoKHSkBGK8GgaVFfWof50/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmCawT%2FbtqBt99hLL0%2F5NoKHSkBGK8GgaVFfWof50%2Fimg.png&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1333&quot; data-origin-height=&quot;2000&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1333&quot; data-origin-height=&quot;2000&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/egVFLz/btqBujqilBv/NsJ2asY4hhlkEEwALjZAIk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/egVFLz/btqBujqilBv/NsJ2asY4hhlkEEwALjZAIk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/egVFLz/btqBujqilBv/NsJ2asY4hhlkEEwALjZAIk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FegVFLz%2FbtqBujqilBv%2FNsJ2asY4hhlkEEwALjZAIk%2Fimg.png&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1333&quot; data-origin-height=&quot;2000&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Log</category>
      <author>독행소년</author>
      <guid isPermaLink="true">https://here4you.tistory.com/237</guid>
      <comments>https://here4you.tistory.com/237#entry237comment</comments>
      <pubDate>Sun, 26 Jan 2020 20:20:03 +0900</pubDate>
    </item>
    <item>
      <title>2020-01-18 | 대전 오월드 &amp;amp; 버드랜드 | Cannon G9X mark2</title>
      <link>https://here4you.tistory.com/236</link>
      <description>&lt;p&gt;대전의 대표 동물원인 오월드에 버드랜드가 생겼다고 해서 가족들과 구경을 다녀왔다.&lt;/p&gt;
&lt;p&gt;버드랜드는 기존의 플라워랜드의 한 켠에 자리 잡고 있는데 온실로 만들어져서 열대 식물과 새들을 구경할 수 있다.&lt;/p&gt;
&lt;p&gt;최근에 구매한 캐논 G9X mark2로 촬영을 했는데 기존의 스마트폰에 비해서는 확실히 좋은 화질을 보여준다. 다만 촬영 실력이 많이 부족해서 문제다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;20200118154521_IMG_0086.JPG&quot; data-origin-width=&quot;4864&quot; data-origin-height=&quot;3648&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b4Ptgv/btqBujqh2E5/Xi7SvLfyvg0XIUDG6rwKw0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b4Ptgv/btqBujqh2E5/Xi7SvLfyvg0XIUDG6rwKw0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b4Ptgv/btqBujqh2E5/Xi7SvLfyvg0XIUDG6rwKw0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb4Ptgv%2FbtqBujqh2E5%2FXi7SvLfyvg0XIUDG6rwKw0%2Fimg.jpg&quot; data-filename=&quot;20200118154521_IMG_0086.JPG&quot; data-origin-width=&quot;4864&quot; data-origin-height=&quot;3648&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;20200118154600_IMG_0090.JPG&quot; data-origin-width=&quot;4864&quot; data-origin-height=&quot;3648&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ctCx2P/btqBuix9pIN/OJPUnGEaHhhbdVEPn050z0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ctCx2P/btqBuix9pIN/OJPUnGEaHhhbdVEPn050z0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ctCx2P/btqBuix9pIN/OJPUnGEaHhhbdVEPn050z0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FctCx2P%2FbtqBuix9pIN%2FOJPUnGEaHhhbdVEPn050z0%2Fimg.jpg&quot; data-filename=&quot;20200118154600_IMG_0090.JPG&quot; data-origin-width=&quot;4864&quot; data-origin-height=&quot;3648&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;20200118154729_IMG_0095.JPG&quot; data-origin-width=&quot;4864&quot; data-origin-height=&quot;3648&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Lgn6H/btqBuQOIbf0/duqahVoeHYlRkpcY0K3zok/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Lgn6H/btqBuQOIbf0/duqahVoeHYlRkpcY0K3zok/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Lgn6H/btqBuQOIbf0/duqahVoeHYlRkpcY0K3zok/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLgn6H%2FbtqBuQOIbf0%2FduqahVoeHYlRkpcY0K3zok%2Fimg.jpg&quot; data-filename=&quot;20200118154729_IMG_0095.JPG&quot; data-origin-width=&quot;4864&quot; data-origin-height=&quot;3648&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1500&quot; data-origin-height=&quot;2000&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/doLmH2/btqBxzyVIdS/4jRSGsjRJlsyY8gkXnrRK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/doLmH2/btqBxzyVIdS/4jRSGsjRJlsyY8gkXnrRK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/doLmH2/btqBxzyVIdS/4jRSGsjRJlsyY8gkXnrRK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdoLmH2%2FbtqBxzyVIdS%2F4jRSGsjRJlsyY8gkXnrRK0%2Fimg.png&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1500&quot; data-origin-height=&quot;2000&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;20200118160215_IMG_0129.JPG&quot; data-origin-width=&quot;4864&quot; data-origin-height=&quot;3648&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1lrbl/btqByFMrdyr/1GK3q5XGqTjiC5OJkWKDYK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1lrbl/btqByFMrdyr/1GK3q5XGqTjiC5OJkWKDYK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1lrbl/btqByFMrdyr/1GK3q5XGqTjiC5OJkWKDYK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1lrbl%2FbtqByFMrdyr%2F1GK3q5XGqTjiC5OJkWKDYK%2Fimg.jpg&quot; data-filename=&quot;20200118160215_IMG_0129.JPG&quot; data-origin-width=&quot;4864&quot; data-origin-height=&quot;3648&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1500&quot; data-origin-height=&quot;2000&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ulhOq/btqBubMLNIq/0LBXUPfp52ka7w24GR98Q1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ulhOq/btqBubMLNIq/0LBXUPfp52ka7w24GR98Q1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ulhOq/btqBubMLNIq/0LBXUPfp52ka7w24GR98Q1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FulhOq%2FbtqBubMLNIq%2F0LBXUPfp52ka7w24GR98Q1%2Fimg.png&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1500&quot; data-origin-height=&quot;2000&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1500&quot; data-origin-height=&quot;2000&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bZqbxc/btqBxzFHBh8/3Nf0pRoNM3DAAtffbpD1xk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bZqbxc/btqBxzFHBh8/3Nf0pRoNM3DAAtffbpD1xk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bZqbxc/btqBxzFHBh8/3Nf0pRoNM3DAAtffbpD1xk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbZqbxc%2FbtqBxzFHBh8%2F3Nf0pRoNM3DAAtffbpD1xk%2Fimg.png&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1500&quot; data-origin-height=&quot;2000&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Log</category>
      <author>독행소년</author>
      <guid isPermaLink="true">https://here4you.tistory.com/236</guid>
      <comments>https://here4you.tistory.com/236#entry236comment</comments>
      <pubDate>Sun, 26 Jan 2020 20:09:21 +0900</pubDate>
    </item>
    <item>
      <title>Flutter 강좌 - [Firebase] Cloud Storage 사용법</title>
      <link>https://here4you.tistory.com/235</link>
      <description>&lt;p style=&quot;font-size: 1.12em; text-align: justify;&quot;&gt;&lt;b&gt;Flutter Code Examples 강좌를 추천합니다.&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;제 블로그에서 Flutter Code Examples 프로젝트를 시작합니다.&lt;/li&gt;
&lt;li&gt;Flutter의 다양한 예제를 소스코드와 실행화면으로 제공합니다.&lt;/li&gt;
&lt;li&gt;또한 모든 예제는 &lt;a href=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Flutter Code Examples&lt;/a&gt; 앱을 통해 테스트 가능합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;a href=&quot;https://here4you.tistory.com/category/Tutorial/Flutter%20with%20App&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Flutter Code Examples 강좌로 메뉴로 이동&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;a href=&quot;https://here4you.tistory.com/213&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Flutter Code Examples 강좌 목록 페이지로 이동&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;a href=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Flutter Code Examples 앱 설치 | Google Play Store로 이동&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1571794790206&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Flutter Code Examples - Google Play 앱&quot; data-og-description=&quot;Are you a beginner at Flutter? Check out the various features of Flutter through the demo. Source code for all demos is also provided.&quot; data-og-host=&quot;play.google.com&quot; data-og-source-url=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&quot; data-og-url=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&amp;amp;hl=ko&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/czLd1P/hyDnjfJW0L/qDPYD8aFvfFMc98kyKiLK0/img.png?width=512&amp;amp;height=250&amp;amp;face=0_0_512_250,https://scrap.kakaocdn.net/dn/McaI6/hyDnmQ5H8S/TnzOISQwiEcJCDXG9Vbo71/img.png?width=600&amp;amp;height=300&amp;amp;face=0_0_600_300&quot;&gt;&lt;a href=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&amp;amp;hl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/czLd1P/hyDnjfJW0L/qDPYD8aFvfFMc98kyKiLK0/img.png?width=512&amp;amp;height=250&amp;amp;face=0_0_512_250,https://scrap.kakaocdn.net/dn/McaI6/hyDnmQ5H8S/TnzOISQwiEcJCDXG9Vbo71/img.png?width=600&amp;amp;height=300&amp;amp;face=0_0_600_300');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;Flutter Code Examples - Google Play 앱&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;Are you a beginner at Flutter? Check out the various features of Flutter through the demo. Source code for all demos is also provided.&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;play.google.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;hr style=&quot;margin: 20px auto 0px; border: none; cursor: pointer !important; z-index: 1; font-size: 0px; line-height: 0; height: 20px; background: url('../image/divider-line.svg') 0px -180px / 200px 200px no-repeat; width: 200px; color: #333333; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;blockquote class=&quot;tx-quote-tistory&quot;&gt;
&lt;p&gt;Flutter 강좌 시즌2 목록 :&amp;nbsp;&lt;a class=&quot;tx-link&quot; href=&quot;https://here4you.tistory.com/149&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://here4you.tistory.com/149&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;이번 강좌에서는 Firebase에서 제공하는 기능 중 하나인 Cloud Storage에 대해서 알아본다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;Cloud Storage(이하 Storage)는 이름 그래도 클라우드 환경의 파일 저장소다. 서비스를 이용하는 사용자의 사진, 문서, 동영상 등의 파일들을 업로드하고 다운로드할 수 있다. 더욱이 업로드된 파일의 URL을 제공하여 접근이 가능하기 때문에 앱에서 사용자의 프로필 사진을 Storage로 업로드한 후 업로드된 프로필 사진의 URL를 이용해서 화면을 구성할 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;이번 강좌도 지난 Flutter Firebase 앱을 이용하여 진행하므로 이전 강좌 포스팅에 대한 선이해가 필요하다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;b&gt;1. Firebase Storage 환경 구성&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;Firebase 콘솔에 진입하여 Storage 메뉴에서 시작하기를 선택한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;이미지 3.png&quot; data-origin-width=&quot;1380&quot; data-origin-height=&quot;1076&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bTsFbk/btqAwj6h2ka/JKpKsSg6AZAZLhuQbHXmkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bTsFbk/btqAwj6h2ka/JKpKsSg6AZAZLhuQbHXmkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bTsFbk/btqAwj6h2ka/JKpKsSg6AZAZLhuQbHXmkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbTsFbk%2FbtqAwj6h2ka%2FJKpKsSg6AZAZLhuQbHXmkk%2Fimg.png&quot; data-filename=&quot;이미지 3.png&quot; data-origin-width=&quot;1380&quot; data-origin-height=&quot;1076&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;첫 번째로 보안 규칙에 대해 설명된다. 다음을 선택한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;이미지 2.png&quot; data-origin-width=&quot;1380&quot; data-origin-height=&quot;1076&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XRtaE/btqAwkqAszd/ikcStfrLVbcCvHWmAO5fQ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XRtaE/btqAwkqAszd/ikcStfrLVbcCvHWmAO5fQ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XRtaE/btqAwkqAszd/ikcStfrLVbcCvHWmAO5fQ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXRtaE%2FbtqAwkqAszd%2FikcStfrLVbcCvHWmAO5fQ1%2Fimg.png&quot; data-filename=&quot;이미지 2.png&quot; data-origin-width=&quot;1380&quot; data-origin-height=&quot;1076&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;다음으로 이용할 Storage의 위치 설정 화면이 나타난다. 기존에 Firestore를 설정하면서 지정된 위치를 이용하게 된다. 완료를 선택한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;이미지 3.png&quot; data-origin-width=&quot;1380&quot; data-origin-height=&quot;1076&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xI2aH/btqAv1rglhy/Z5kaOoCn5J0rQmkT225Et1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xI2aH/btqAv1rglhy/Z5kaOoCn5J0rQmkT225Et1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xI2aH/btqAv1rglhy/Z5kaOoCn5J0rQmkT225Et1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxI2aH%2FbtqAv1rglhy%2FZ5kaOoCn5J0rQmkT225Et1%2Fimg.png&quot; data-filename=&quot;이미지 3.png&quot; data-origin-width=&quot;1380&quot; data-origin-height=&quot;1076&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;설정이 완료되면 다음과 같이 화면이 나타난다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;이미지 4.png&quot; data-origin-width=&quot;1063&quot; data-origin-height=&quot;877&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cF6Kn5/btqAy2BZ43V/u9bARaxQOYeajMndRCgPD1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cF6Kn5/btqAy2BZ43V/u9bARaxQOYeajMndRCgPD1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cF6Kn5/btqAy2BZ43V/u9bARaxQOYeajMndRCgPD1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcF6Kn5%2FbtqAy2BZ43V%2Fu9bARaxQOYeajMndRCgPD1%2Fimg.png&quot; data-filename=&quot;이미지 4.png&quot; data-origin-width=&quot;1063&quot; data-origin-height=&quot;877&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;프로필 사진을 업로드할 폴더 profile을 생성하자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;이미지 5.png&quot; data-origin-width=&quot;1063&quot; data-origin-height=&quot;877&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c9FvXa/btqAwjypA3i/AD426mvs1FpJ9pGL4erUIK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c9FvXa/btqAwjypA3i/AD426mvs1FpJ9pGL4erUIK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c9FvXa/btqAwjypA3i/AD426mvs1FpJ9pGL4erUIK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc9FvXa%2FbtqAwjypA3i%2FAD426mvs1FpJ9pGL4erUIK%2Fimg.png&quot; data-filename=&quot;이미지 5.png&quot; data-origin-width=&quot;1063&quot; data-origin-height=&quot;877&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;profile 폴더가 생성되었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;이미지 6.png&quot; data-origin-width=&quot;1063&quot; data-origin-height=&quot;877&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dm3HaP/btqAzxu0w2t/KOHR5DNloyZ21JXlfcQL40/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dm3HaP/btqAzxu0w2t/KOHR5DNloyZ21JXlfcQL40/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dm3HaP/btqAzxu0w2t/KOHR5DNloyZ21JXlfcQL40/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdm3HaP%2FbtqAzxu0w2t%2FKOHR5DNloyZ21JXlfcQL40%2Fimg.png&quot; data-filename=&quot;이미지 6.png&quot; data-origin-width=&quot;1063&quot; data-origin-height=&quot;877&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;보안 규칙(Rules) 메뉴로 이동해보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;이미지 7.png&quot; data-origin-width=&quot;1348&quot; data-origin-height=&quot;877&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/31cpM/btqAv1kvxnA/kCykq1hSvs2yKzX8bKsSKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/31cpM/btqAv1kvxnA/kCykq1hSvs2yKzX8bKsSKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/31cpM/btqAv1kvxnA/kCykq1hSvs2yKzX8bKsSKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F31cpM%2FbtqAv1kvxnA%2FkCykq1hSvs2yKzX8bKsSKk%2Fimg.png&quot; data-filename=&quot;이미지 7.png&quot; data-origin-width=&quot;1348&quot; data-origin-height=&quot;877&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;기본값으로, request.auth 값이 null 이 아니면 모든 경로에 대해 read, write가 가능하도록 설정되어 있다. 즉 Firebase Authentication 기능으로 통해 로그인된 사용자라면 모든 경로에서 읽고 쓰기가 가능하다는 뜻이다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;b&gt;2. Flutter Firebase 앱 수정 #1 - 이미지 피커 플러그인을 이용한 프로필 사진 선택&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;Storage를 이용하기 전에 프로필 사진으로 이용할 사진을 선택하는 기능을 구현해 보자. pubspec.yaml 파일에 이미지 피커 플러그인을 추가한 후 Packages get을 실행하자.&lt;/p&gt;
&lt;pre id=&quot;code_1576639395058&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dependencies:
  image_picker: ^0.6.2+2&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;2019년 12월 18일 현재 image_picker 플러그인은 0.6.2+3 버전까지 출시되었다. 그런데 이 버전은 Flutter SDK 1.12.13 버전을 요구하며 기존의 Flutter Firebase 앱의 SDK 보다 상위 버전이다. SDK를 최신 버전으로 업그레이드한 후 진행할 수도 있으나 그럴 경우 다른 플러그인과의 버전 충돌이 발생할 수 있으므로 기존의 SDK를 지원하는 0.6.2+2 버전을 이용한다. Flutter SDK의 업그레이드와 다운그레이드 방법에 대해서는 기존 강좌 &lt;a href=&quot;https://here4you.tistory.com/234&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;포스팅&lt;/a&gt;을 참고한다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;다음과 같이 새로운 페이지를 구현한다.&lt;/p&gt;
&lt;pre id=&quot;code_1576639640450&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'dart:io';

import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';

CloudStorageDemoState pageState;

class CloudStorageDemo extends StatefulWidget {
  @override
  CloudStorageDemoState createState() {
    pageState = CloudStorageDemoState();
    return pageState;
  }
}

class CloudStorageDemoState extends State&amp;lt;CloudStorageDemo&amp;gt; {
  File _image;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(&quot;Cloud Storage Demo&quot;)),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: &amp;lt;Widget&amp;gt;[
            CircleAvatar(
              backgroundImage:
                  (_image != null) ? FileImage(_image) : NetworkImage(&quot;&quot;),
              radius: 30,
            ),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: &amp;lt;Widget&amp;gt;[
                RaisedButton(
                  child: Text(&quot;Gallery&quot;),
                  onPressed: () {
                    _uploadImageToStorage(ImageSource.gallery);
                  },
                ),
                RaisedButton(
                  child: Text(&quot;Camera&quot;),
                  onPressed: () {
                    _uploadImageToStorage(ImageSource.camera);
                  },
                )
              ],
            )
          ],
        ),
      ),
    );
  }

  void _uploadImageToStorage(ImageSource source) async {
    File image = await ImagePicker.pickImage(source: source);

    if (image == null) return;
    setState(() {
      _image = image;
    });
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;이미지 피커 플러그인을 이용하면, 갤러리에서 사진을 선택하거나 카메라를 통해 새로운 사진을 촬영하여 프로필 이미지를 획득할 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;실행 화면은 다음과 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;anigif.gif&quot; data-origin-width=&quot;389&quot; data-origin-height=&quot;800&quot; width=&quot;300&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/czV7Ur/btqAy0KZsWX/6P8ogGjfwWc9mBUDHf1cIk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/czV7Ur/btqAy0KZsWX/6P8ogGjfwWc9mBUDHf1cIk/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/czV7Ur/btqAy0KZsWX/6P8ogGjfwWc9mBUDHf1cIk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/czV7Ur/btqAy0KZsWX/6P8ogGjfwWc9mBUDHf1cIk/img.gif&quot; data-filename=&quot;anigif.gif&quot; data-origin-width=&quot;389&quot; data-origin-height=&quot;800&quot; width=&quot;300&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333;&quot;&gt;3. Flutter Firebase 앱 수정 #2 - Storage 플러그인을 이용하여 이미지 업로드 기능 구현&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;pubspec.yaml 파일에 Storage 플러그인을 추가한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1576639932938&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dependencies:
  firebase_storage: ^3.0.10&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;이번 플러그인 역시 최신 버전이 아닌 기존 Flutter Firebase에서 사용하는 SDK와 호환되는 버전을 이용했다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;그리고 코드를 다음과 같이 수정한다.&lt;/p&gt;
&lt;pre id=&quot;code_1576641444342&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'dart:io';

import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'package:firebase_storage/firebase_storage.dart';

CloudStorageDemoState pageState;

class CloudStorageDemo extends StatefulWidget {
  @override
  CloudStorageDemoState createState() {
    pageState = CloudStorageDemoState();
    return pageState;
  }
}

class CloudStorageDemoState extends State&amp;lt;CloudStorageDemo&amp;gt; {
  File _image;
  FirebaseAuth _firebaseAuth = FirebaseAuth.instance;
  FirebaseUser _user;
  FirebaseStorage _firebaseStorage = FirebaseStorage.instance;
  String _profileImageURL = &quot;&quot;;

  @override
  void initState() {
    super.initState();
    _prepareService();
  }

  void _prepareService() async {
    _user = await _firebaseAuth.currentUser();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(&quot;Cloud Storage Demo&quot;)),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: &amp;lt;Widget&amp;gt;[
            // 업로드할 이미지를 출력할 CircleAvatar
            CircleAvatar(
              backgroundImage:
                  (_image != null) ? FileImage(_image) : NetworkImage(&quot;&quot;),
              radius: 30,
            ),
            // 업로드할 이미지를 선택할 이미지 피커 호출 버튼
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: &amp;lt;Widget&amp;gt;[
                RaisedButton(
                  child: Text(&quot;Gallery&quot;),
                  onPressed: () {
                    _uploadImageToStorage(ImageSource.gallery);
                  },
                ),
                RaisedButton(
                  child: Text(&quot;Camera&quot;),
                  onPressed: () {
                    _uploadImageToStorage(ImageSource.camera);
                  },
                )
              ],
            ),
            Divider(
              color: Colors.grey,
            ),
            // 업로드 된 이미지를 출력할 CircleAvatar
            CircleAvatar(
              backgroundImage: NetworkImage(_profileImageURL),
              radius: 30,
            ),
            // 업로드 된 이미지의 URL
            Padding(
              padding: const EdgeInsets.all(8.0),
              child: Text(_profileImageURL),
            )
          ],
        ),
      ),
    );
  }

  void _uploadImageToStorage(ImageSource source) async {
    File image = await ImagePicker.pickImage(source: source);

    if (image == null) return;
    setState(() {
      _image = image;
    });

    // 프로필 사진을 업로드할 경로와 파일명을 정의. 사용자의 uid를 이용하여 파일명의 중복 가능성 제거
    StorageReference storageReference =
        _firebaseStorage.ref().child(&quot;profile/${_user.uid}&quot;);

    // 파일 업로드
    StorageUploadTask storageUploadTask = storageReference.putFile(_image);

    // 파일 업로드 완료까지 대기
    await storageUploadTask.onComplete;

    // 업로드한 사진의 URL 획득
    String downloadURL = await storageReference.getDownloadURL();

    // 업로드된 사진의 URL을 페이지에 반영
    setState(() {
      _profileImageURL = downloadURL;
    });
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;소스코드를 살펴보면,&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;우선 업로드할 프로필 이미지의 파일명을 결정해야 한다. 중복된 파일명이 발생하지 않으려면 유니크한 파일명이 필요한데 쉽게 이용할 수 있는 것이 사용자의 UID 값이다. 이 값은 지난 강좌에서 다룬 Fireabse 인증의 uid를 이용한다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;화면 구성은 기존 코드에서 업로드한 이미지를 출력할 CircleAvatar 위젯과 업로드한 이미지의 URL을 출력할 Text 위젯을 추가했다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;span&gt;_uploadImageToStorage 메서드에서는 이미지 피커를 통해 업로드할 이미지를 선택하는 기존 코드에 선택된 이미지를 업로드하고 업로드한 이미지의 URL를 획득하는 코드가 추가되었다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;실행화면은 다음과 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;anigif.gif&quot; data-origin-width=&quot;389&quot; data-origin-height=&quot;800&quot; width=&quot;300&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ckR7wX/btqAyHSrwW4/gNEHBBimvFyQysqMFonwQK/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ckR7wX/btqAyHSrwW4/gNEHBBimvFyQysqMFonwQK/img.gif&quot; data-alt=&quot;.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ckR7wX/btqAyHSrwW4/gNEHBBimvFyQysqMFonwQK/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/ckR7wX/btqAyHSrwW4/gNEHBBimvFyQysqMFonwQK/img.gif&quot; data-filename=&quot;anigif.gif&quot; data-origin-width=&quot;389&quot; data-origin-height=&quot;800&quot; width=&quot;300&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;이번 강좌에서는 Firebase의 Cloud Storage의 사용법에 대해서 알아봤다. Storage를 이용하면 쉽게 파일을 업로드하고 업로드한 파일의 URL을 이용해서 쉽게 접근이 가능하다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Development/Flutter</category>
      <author>독행소년</author>
      <guid isPermaLink="true">https://here4you.tistory.com/235</guid>
      <comments>https://here4you.tistory.com/235#entry235comment</comments>
      <pubDate>Wed, 18 Dec 2019 13:07:20 +0900</pubDate>
    </item>
    <item>
      <title>Flutter 강좌 - Flutter SDK Upgrade &amp;amp; Downgrade</title>
      <link>https://here4you.tistory.com/234</link>
      <description>&lt;p style=&quot;font-size: 1.12em; text-align: justify;&quot;&gt;&lt;b&gt;Flutter Code Examples 강좌를 추천합니다.&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;제 블로그에서 Flutter Code Examples 프로젝트를 시작합니다.&lt;/li&gt;
&lt;li&gt;Flutter의 다양한 예제를 소스코드와 실행화면으로 제공합니다.&lt;/li&gt;
&lt;li&gt;또한 모든 예제는 &lt;a href=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Flutter Code Examples&lt;/a&gt; 앱을 통해 테스트 가능합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;a href=&quot;https://here4you.tistory.com/category/Tutorial/Flutter%20with%20App&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Flutter Code Examples 강좌로 메뉴로 이동&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;a href=&quot;https://here4you.tistory.com/213&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Flutter Code Examples 강좌 목록 페이지로 이동&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;a href=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Flutter Code Examples 앱 설치 | Google Play Store로 이동&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1571794790206&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Flutter Code Examples - Google Play 앱&quot; data-og-description=&quot;Are you a beginner at Flutter? Check out the various features of Flutter through the demo. Source code for all demos is also provided.&quot; data-og-host=&quot;play.google.com&quot; data-og-source-url=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&quot; data-og-url=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&amp;amp;hl=ko&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/czLd1P/hyDnjfJW0L/qDPYD8aFvfFMc98kyKiLK0/img.png?width=512&amp;amp;height=250&amp;amp;face=0_0_512_250,https://scrap.kakaocdn.net/dn/McaI6/hyDnmQ5H8S/TnzOISQwiEcJCDXG9Vbo71/img.png?width=600&amp;amp;height=300&amp;amp;face=0_0_600_300&quot;&gt;&lt;a href=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&amp;amp;hl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/czLd1P/hyDnjfJW0L/qDPYD8aFvfFMc98kyKiLK0/img.png?width=512&amp;amp;height=250&amp;amp;face=0_0_512_250,https://scrap.kakaocdn.net/dn/McaI6/hyDnmQ5H8S/TnzOISQwiEcJCDXG9Vbo71/img.png?width=600&amp;amp;height=300&amp;amp;face=0_0_600_300');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;Flutter Code Examples - Google Play 앱&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;Are you a beginner at Flutter? Check out the various features of Flutter through the demo. Source code for all demos is also provided.&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;play.google.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;hr style=&quot;margin: 20px auto 0px; border: none; cursor: pointer !important; z-index: 1; font-size: 0px; line-height: 0; height: 20px; background: url('../image/divider-line.svg') 0px -180px / 200px 200px no-repeat; width: 200px; color: #333333; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;blockquote class=&quot;tx-quote-tistory&quot;&gt;
&lt;p&gt;Flutter 강좌 시즌2 목록 :&amp;nbsp;&lt;a class=&quot;tx-link&quot; href=&quot;https://here4you.tistory.com/149&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://here4you.tistory.com/149&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;최근 Firebase와 관련된 플러그인들의 버전 업데이트가 이루어졌다. 무엇이 바뀌었는지 확인하고 싶어 pubspec.yaml 파일에 기재된 플러그인들의 버전을 최신 버전으로 변경한 후 packages get을 하려고 하니 Flutter SDK 1.12에 종속된다는 에러가 발생한다. 새 버전의 Flutter SDK의 출시에 앞서 Firebase 플러그인들이 먼저 업데이트된 것이다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;Flutter의 공식 사이트에는 어제만 해도 1.9.1 v1.9.1+hotfix.6을 최신 버전으로 제공하고 있었는데 오늘보니 최신 버전으로 v1.12.13+hotfix.5가 출시되었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;이미지 3.png&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;462&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0bxyh/btqAmuggrDR/FBE4FTr0AVAQBNAG4O4rF1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0bxyh/btqAmuggrDR/FBE4FTr0AVAQBNAG4O4rF1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0bxyh/btqAmuggrDR/FBE4FTr0AVAQBNAG4O4rF1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0bxyh%2FbtqAmuggrDR%2FFBE4FTr0AVAQBNAG4O4rF1%2Fimg.png&quot; data-filename=&quot;이미지 3.png&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;462&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Flutter SDK 업그레이드&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;다음의 명령어를 통해 최신 Flutter SDK의 설치가 가능하다.&lt;/p&gt;
&lt;pre id=&quot;code_1576119728576&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$&amp;gt; flutter upgrade&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;Android Studio를 사용한다면 터미널 창에서 위의 명령어를 직접 입력하거나, pubspec.yaml 파일을 열어서 화면 상단의 Flutter upgrade 버튼을 클릭하면 된다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;업그레이드가 되고 몇몇 Firebase 플러그인들을 최신 버전으로 바꾼후 Package get를 시도하니 에러가 발생한다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;다른 플러그인들이 아직 Flutter SDK 1.12.13을 받아들인 준비가 안되어서인지 각 플러그인이 참조하는 다른 플러그인들의 버전이 각기 달라 버전 충돌이 발생했다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;과감히 기존 Flutter SDK로 돌아가야한다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot;&gt;
&lt;li&gt;Flutter SDK 다운그레이드(버전 변경)&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;다음 명령어를 통해 설치 가능한 Flutter SDK의 버전 정보를 확인할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1576119972071&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$&amp;gt; flutter version&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;엄청 많다.&lt;/p&gt;
&lt;pre id=&quot;code_1576120005918&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$&amp;gt;flutter version
v1.12.13+hotfix.6
v1.12.13+hotfix.5
v1.12.13+hotfix.4
v1.12.13+hotfix.3
v1.12.13+hotfix.2
v1.13.0
v1.12.13+hotfix.1
v1.12.16
v1.12.15
v1.12.14
v1.12.13
v1.12.12
v1.12.11
v1.12.10
이하 생략...&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;SDK를 다운그레이드(혹은 특정 버전으로 스위치)하기 위해서는 다음과 같은 명령어를 이용한다.&lt;/p&gt;
&lt;pre id=&quot;code_1576120081044&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$&amp;gt;flutter version [원하는 버전]&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;기존의 v1.9.1+hotfix.6으로 스위치한다.&lt;/p&gt;
&lt;pre id=&quot;code_1576120117043&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$&amp;gt;flutter version v1.9.1+hotfix.6
Switching Flutter to version 1.9.1+hotfix.6

Downloading engine...
Checking Dart SDK version...
Downloading Dart SDK from Flutter engine b863200c37df4ed378042de11c4e9ff34e4e58c9...
Unzipping Dart SDK...
Building flutter tool...
Running pub upgrade...
Downloading android-arm-profile/windows-x64 tools...                1.4s
Downloading android-arm-release/windows-x64 tools...                1.1s
Downloading android-arm64-profile/windows-x64 tools...              1.5s
Downloading android-arm64-release/windows-x64 tools...              2.5s
Downloading android-x86 tools...                                    8.1s
Downloading android-x64 tools...                                    5.4s
Downloading android-arm tools...                                    3.3s
Downloading android-arm-profile tools...                            2.0s
Downloading android-arm-release tools...                            2.3s
Downloading android-arm64 tools...                                  4.6s
Downloading android-arm64-profile tools...                          3.7s
Downloading android-arm64-release tools...                          2.2s
Downloading package sky_engine...                                   0.9s
Downloading common tools...                                         4.4s
Downloading common tools...                                         2.7s
Downloading windows-x64 tools...                                    7.4s

Flutter 1.9.1+hotfix.6 &amp;bull; channel unknown &amp;bull; unknown source
Framework &amp;bull; revision 68587a0916 (3 months ago) &amp;bull; 2019-09-13 19:46:58 -0700
Engine &amp;bull; revision b863200c37
Tools &amp;bull; Dart 2.7.0

Running &quot;flutter pub upgrade&quot; in xxx...         14.1s

Running flutter doctor...
Doctor summary (to see all details, run flutter doctor -v):
[&amp;radic;] Flutter (Channel unknown, v1.9.1+hotfix.6, on Microsoft Windows [Version 10.0.18362.476], locale ko-KR)
[&amp;radic;] Android toolchain - develop for Android devices (Android SDK version 28.0.3)
[&amp;radic;] Android Studio (version 3.5)
[&amp;radic;] VS Code (version 1.40.2)
[&amp;radic;] Connected device (2 available)

&amp;bull; No issues found!
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;기존 버전으로 변경하고 친절하게 flutter doctor까지 자동 실행해준다. 아무런 이슈가 없으니 packages get을 다시 실행하자 별 문제없이 플러그인들이 설치되었다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Development/Flutter</category>
      <author>독행소년</author>
      <guid isPermaLink="true">https://here4you.tistory.com/234</guid>
      <comments>https://here4you.tistory.com/234#entry234comment</comments>
      <pubDate>Thu, 12 Dec 2019 12:13:02 +0900</pubDate>
    </item>
    <item>
      <title>Flutter 강좌 - 로그아웃 하고 첫 화면(페이지)로 이동하는 법 | Navigator.pop | pushReplacementNamed</title>
      <link>https://here4you.tistory.com/233</link>
      <description>&lt;p style=&quot;font-size: 1.12em; text-align: justify;&quot;&gt;&lt;b&gt;Flutter Code Examples 강좌를 추천합니다.&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;제 블로그에서 Flutter Code Examples 프로젝트를 시작합니다.&lt;/li&gt;
&lt;li&gt;Flutter의 다양한 예제를 소스코드와 실행화면으로 제공합니다.&lt;/li&gt;
&lt;li&gt;또한 모든 예제는 &lt;a href=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Flutter Code Examples&lt;/a&gt; 앱을 통해 테스트 가능합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;a href=&quot;https://here4you.tistory.com/category/Tutorial/Flutter%20with%20App&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Flutter Code Examples 강좌로 메뉴로 이동&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;a href=&quot;https://here4you.tistory.com/213&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Flutter Code Examples 강좌 목록 페이지로 이동&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;a href=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Flutter Code Examples 앱 설치 | Google Play Store로 이동&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1571794790206&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Flutter Code Examples - Google Play 앱&quot; data-og-description=&quot;Are you a beginner at Flutter? Check out the various features of Flutter through the demo. Source code for all demos is also provided.&quot; data-og-host=&quot;play.google.com&quot; data-og-source-url=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&quot; data-og-url=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&amp;amp;hl=ko&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/czLd1P/hyDnjfJW0L/qDPYD8aFvfFMc98kyKiLK0/img.png?width=512&amp;amp;height=250&amp;amp;face=0_0_512_250,https://scrap.kakaocdn.net/dn/McaI6/hyDnmQ5H8S/TnzOISQwiEcJCDXG9Vbo71/img.png?width=600&amp;amp;height=300&amp;amp;face=0_0_600_300&quot;&gt;&lt;a href=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&amp;amp;hl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://play.google.com/store/apps/details?id=com.h4u.flutter_examples&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/czLd1P/hyDnjfJW0L/qDPYD8aFvfFMc98kyKiLK0/img.png?width=512&amp;amp;height=250&amp;amp;face=0_0_512_250,https://scrap.kakaocdn.net/dn/McaI6/hyDnmQ5H8S/TnzOISQwiEcJCDXG9Vbo71/img.png?width=600&amp;amp;height=300&amp;amp;face=0_0_600_300');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;Flutter Code Examples - Google Play 앱&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;Are you a beginner at Flutter? Check out the various features of Flutter through the demo. Source code for all demos is also provided.&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;play.google.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;hr style=&quot;margin: 20px auto 0px; border: none; cursor: pointer !important; z-index: 1; font-size: 0px; line-height: 0; height: 20px; background: url('../image/divider-line.svg') 0px -180px / 200px 200px no-repeat; width: 200px; color: #333333; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;blockquote class=&quot;tx-quote-tistory&quot;&gt;
&lt;p&gt;Flutter 강좌 시즌2 목록 :&amp;nbsp;&lt;a class=&quot;tx-link&quot; href=&quot;https://here4you.tistory.com/149&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://here4you.tistory.com/149&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;이번 강좌는 화면을 push, pop, replace에 대한 간단한 팁을 다룬다. 이미 기존 강좌에서 다루긴 했다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Push (화면 적재)&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;앱에 화면을 적재할 때에는 Navigator.push 메서드를 이용한다. push 메서드를 호출할 때마다 기존 화면 위에 새로운 화면이 얹어지는 형태다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Pop (화면 제거)&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;현재의 화면을 제거하려면 Navigator.pop 메서드를 이용한다. pop 메서드가 호출될 때마다 현재 화면이 사라지고 그 하단의 화면이 다시 나타난다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Replace (화면 전환)&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;새로운 화면을 사용자에게 보이기 위해 push를 이용하게 되면 새 화면이 기존 화면 위에 적재되게 된다. 만약 여러 페이지를 push 한 상태에서 맨 처음의 화면으로 돌아가려면 push를 수행한 횟수만큼 back 버튼을 눌러서 적재되었던 화면들을 pop 해야 한다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;어찌 보면 당연하지만 어느 경우에는 불편할 수도 있다. 예를 들어 앱이 실행될 때 처음 한 번만 보이고 앱이 종료될 때까지 사용자에게 다시 보이고 싶지 않은 페이지가 있을 수도 있다. 이런 경우에 화면 전환을 이용한다. 이때에는 Navigator.replace 메서드를 이용한다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;다음과 같은 화면 구성을 가지는 앱이 있다고 가정해보자.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;IndexPage(로그인 기능 포함) -&amp;gt; MainPage -&amp;gt; SettingPage(로그아웃 기능 포함)&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;text-align: justify;&quot;&gt;IndexPage -&amp;gt; MainPage 과정&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;앱이 실행되면 IndexPage가 처음으로 실행된 후 로그인 과정에 성공하면 MainPage가 출력된다. 일반적으로 IndexPage는 앱을 실행할 때 한 번만 사용자에게 보이면 되고 앱이 종료될 때 다시 보일 필요가 없다. 그러므로 IndexPage에서 MainPage로 이동할 때에는 화면 전환(replace)으로 처리하는 것이 일반적이다. 결과 적으로 IndexPage는 화면 스택에서 삭제되고, MainPage가 화면 스택의 root가 된다. 그래서 MainPage를 보고 있는 사용자가 back 버튼을 누르면 앱이 종료된다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;text-align: justify;&quot;&gt;MainPage &amp;lt;-&amp;gt; SettingPage 과정&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;MainPage를 이용하는 사용자가 앱의 설정을 위해 SettingPage로 이동해야 할 경우에는 화면 적재(push)로 처리하는 것이 일반적이다. 설정이 완료되면 back 버튼을 눌러 SettingPage를 삭제(pop)하고 MainPage로 이동한다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;text-align: justify;&quot;&gt;SettingPage -&amp;gt; IndexPage 과정&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;그런데 SettingPage의 기능 중 로그아웃 기능을 이용할 때에는 조금 특별한 처리가 필요하다. 이 경우에는 로그아웃을 하고 앱을 종료하는 것이 아닌 IndexPage로 이동하는 것이 일반적이다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;우선 IndexPage -&amp;gt; MainPage 과정에서 IndexPage를 MainPage로 전환하면서 IndexPage는 화면 스택에서 삭제되었으므로 IndexPage를 새롭게 생성해야 화면 스택에 적재해야 한다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;그럼 IndexPage를 push 해야 할까? push 하게 된다면 화면 스택에는 MainPage -&amp;gt; SettingPage -&amp;gt; IndexPage 형태로 화면이 얹어진다. IndexPage에서 back 버튼을 누르면 SettingPage와 MainPage로 전환되는 이상한 형태가 된다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;일단 push가 답이 아니므로 SettingPage를 IndexPage로 replace 해야 한다. 그런데 이경우에도 화면 스택에는 MainPage -&amp;gt; IndexPage형태로 화면이 구성된다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;로그아웃을 완료한 화면 스택에는 IndexPage 하나만 남아야 하는데 이 경우에는 pop과 replace를 연달아서 수행하면 된다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;pop 처리에서는 SettingPage가 제거되게 되고, replace 처리에서는 MainPage가 IndexPage로 전환되는 것이다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;만약 replace 과정 없이 push만을 통해 여러 페이지를 적재한 상태에서 첫 번째 화면만 남기고 남은 페이지들을 삭제하고자 할 때에는 다음과 같이 popUntil 메서드를 이용할 수도 있다고 한다&lt;span style=&quot;color: #333333;&quot;&gt;(해보지는 않았다)&lt;/span&gt;.&lt;/p&gt;
&lt;pre id=&quot;code_1575613548341&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Navigator.popUntil(context, ModalRoute.withName(Navigator.defaultRouteName))&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;첫번째 화면까지는 아니지만 복수개의 페이지를 pop 하고자 할 때에는 다음과 같이 처리할 수도 있다고 한다(해보지는 않았다).&lt;/p&gt;
&lt;pre id=&quot;code_1575613714087&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;count = 0;
Navigator.popUntil(context, (route) {
    return count++ == 2;
});&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Development/Flutter</category>
      <author>독행소년</author>
      <guid isPermaLink="true">https://here4you.tistory.com/233</guid>
      <comments>https://here4you.tistory.com/233#entry233comment</comments>
      <pubDate>Fri, 6 Dec 2019 15:30:19 +0900</pubDate>
    </item>
  </channel>
</rss>