<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>re_brother.log</title>
        <link>https://velog.io/</link>
        <description>I hope the All-Rounder Developer &amp; Researcher</description>
        <lastBuildDate>Tue, 08 Feb 2022 02:16:37 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>re_brother.log</title>
            <url>https://images.velog.io/images/re_brother/profile/07e8e4db-4942-4550-b944-3680fc068a59/제목 없음.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. re_brother.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/re_brother" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[TIL] Android Layout : February 8, 2022]]></title>
            <link>https://velog.io/@re_brother/TIL-Android-Layout-February-8-2022</link>
            <guid>https://velog.io/@re_brother/TIL-Android-Layout-February-8-2022</guid>
            <pubDate>Tue, 08 Feb 2022 02:16:37 GMT</pubDate>
            <description><![CDATA[<h2 id="layout">Layout</h2>
<h3 id="android-size-unit">Android Size Unit</h3>
<p>안드로이드 위젯의 크기를 지정할 때 사용되는 단위에 대한 정리
<img src="https://images.velog.io/images/re_brother/post/3cd3bbc7-35d5-4ff7-8335-9e39b1c37da7/image.png" alt="">
안드로이드 크기 단위에는 총 6가지(<code>dp</code>, <code>in</code>, <code>mm</code>, <code>pt</code>, <code>px</code>, <code>sp</code>)가 있다.
위젯의 크기를 설정할 때는 <code>wrap_content</code>, <code>match_parent</code> 또는 <code>dp</code>를 사용.
텍스트 사이즈는 <code>sp</code> 단위를 사용하는 것을 권고.</p>
<blockquote>
</blockquote>
<p><strong>1. px (pixel)</strong>
실제 픽셀 수를 뜻합니다. px을 이용하면 해상도가 다른 디바이스마다 실제 크기가 다르게 보이며 심지어 이미지가 깨질 수도 있습니다.
<strong>2. dp / dip (Density Independent Pixel)</strong>
화면의 크기를 기준으로 하며, 어떠한 화면의 해상도에도 같은 비율로 출력해줍니다. px만으로 다양한 디바이스의 해상도를 지원할 수 없기 때문에 자주 쓰입니다.
<strong>3. dpi (Dots per Inch)</strong>
1인치에 들어있는 픽셀 수를 뜻하며, 안드로이드에서는 160을 기본으로 합니다.
<strong>4. sp / sip (Scale Independent Pixel)</strong>
글꼴의 단위로 자주 쓰입니다. dp와 비슷하지만 사용자가 선택한 글꼴에 따라 달라집니다.
<strong>5. in (Inch)</strong>
실제 물리적인 길이(2.54cm)입니다.
<strong>6. mm (milli meter)</strong>
네. 생각하시는 그 mm 맞습니다. 실제 물리적인 길이입니다.
<strong>7. em</strong>
글꼴과 상관 없이 동일한 텍스트의 크기를 출력합니다.<br>
<a href="https://gdbagooni.tistory.com/16">[참조] [Android Studio] 길이 단위 - dp, dpi, px, sp 의 차이점 </a>
<a href="https://offbyone.tistory.com/229">[참조] 안드로이드 크기 단위(dp, sp, pt, px, mm, in)</a></p>
<h3 id="linearlayout">LinearLayout</h3>
<table>
<thead>
<tr>
<th align="center">Properties</th>
<th align="center">Description</th>
</tr>
</thead>
<tbody><tr>
<td align="center">Orientation</td>
<td align="center">horizontal, vertical 정렬 방향 설정</td>
</tr>
<tr>
<td align="center">Weight</td>
<td align="center">가중치 설정</td>
</tr>
</tbody></table>
<h4 id="orientationhorizontal">Orientation(Horizontal)</h4>
<pre><code class="language-xml">&lt;LinearLayout xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    xmlns:tools=&quot;http://schemas.android.com/tools&quot;
    android:layout_width=&quot;match_parent&quot;
    android:layout_height=&quot;match_parent&quot;
    tools:context=&quot;.MainActivity&quot;
    android:orientation=&quot;horizontal&quot;&gt;
    &lt;TextView
        android:layout_width=&quot;match_parent&quot;
        android:layout_height=&quot;match_parent&quot;
        android:text=&quot;TextView1&quot;
        android:layout_weight=&quot;1&quot;
        android:background=&quot;#ff9966&quot;
        android:layout_margin=&quot;5dp&quot;
        /&gt;
    &lt;TextView
        android:layout_width=&quot;match_parent&quot;
        android:layout_height=&quot;match_parent&quot;
        android:text=&quot;TextView2&quot;
        android:layout_weight=&quot;1&quot;
        android:background=&quot;#66ffcc&quot;
        android:layout_margin=&quot;5dp&quot;
        /&gt;
    &lt;TextView
        android:layout_width=&quot;match_parent&quot;
        android:layout_height=&quot;match_parent&quot;
        android:text=&quot;TextView3&quot;
        android:layout_weight=&quot;1&quot;
        android:background=&quot;#99ff00&quot;
        android:layout_margin=&quot;5dp&quot;
        /&gt;
    &lt;TextView
        android:layout_width=&quot;match_parent&quot;
        android:layout_height=&quot;match_parent&quot;
        android:text=&quot;TextView4&quot;
        android:layout_weight=&quot;1&quot;
        android:background=&quot;#cc00ff&quot;
        android:layout_margin=&quot;5dp&quot;
        /&gt;
&lt;/LinearLayout&gt;</code></pre>
<h4 id="result">Result</h4>
<p><img src="https://images.velog.io/images/re_brother/post/41bfbdee-b311-493d-8af8-dfe633fd1cc7/image.png" alt=""></p>
<h4 id="orientationvertical">Orientation(Vertical)</h4>
<pre><code class="language-xml">&lt;LinearLayout xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    xmlns:tools=&quot;http://schemas.android.com/tools&quot;
    android:layout_width=&quot;match_parent&quot;
    android:layout_height=&quot;match_parent&quot;
    tools:context=&quot;.MainActivity&quot;
    android:orientation=&quot;vertical&quot;&gt;
    &lt;TextView
        android:layout_width=&quot;match_parent&quot;
        android:layout_height=&quot;match_parent&quot;
        android:text=&quot;TextView1&quot;
        android:layout_weight=&quot;1&quot;
        android:background=&quot;#ff9966&quot;
        android:layout_margin=&quot;5dp&quot;
        /&gt;
    &lt;TextView
        android:layout_width=&quot;match_parent&quot;
        android:layout_height=&quot;match_parent&quot;
        android:text=&quot;TextView2&quot;
        android:layout_weight=&quot;1&quot;
        android:background=&quot;#66ffcc&quot;
        android:layout_margin=&quot;5dp&quot;
        /&gt;
    &lt;TextView
        android:layout_width=&quot;match_parent&quot;
        android:layout_height=&quot;match_parent&quot;
        android:text=&quot;TextView3&quot;
        android:layout_weight=&quot;1&quot;
        android:background=&quot;#99ff00&quot;
        android:layout_margin=&quot;5dp&quot;
        /&gt;
    &lt;TextView
        android:layout_width=&quot;match_parent&quot;
        android:layout_height=&quot;match_parent&quot;
        android:text=&quot;TextView4&quot;
        android:layout_weight=&quot;1&quot;
        android:background=&quot;#cc00ff&quot;
        android:layout_margin=&quot;5dp&quot;
        /&gt;
&lt;/LinearLayout&gt;
</code></pre>
<h4 id="result-1">Result</h4>
<p><img src="https://images.velog.io/images/re_brother/post/2bd92d76-5fc8-4105-8f39-8d22710dda6c/image.png" alt=""></p>
<h3 id="relativelayout">RelativeLayout</h3>
<h4 id="target-base">Target base</h4>
<blockquote>
</blockquote>
<p><code>layout_to[location]Of=&quot;[target]&quot;</code> : <code>[target]</code>의 <code>[location]</code> 지정
<code>location</code> : Right, Left, above, below, StartOf, EndOf</p>
<blockquote>
</blockquote>
<p><code>layout_align[location]=&quot;[target]&quot;</code> : <code>[target]</code>의 <code>[location]</code> 기준 정렬
<code>location</code> : Right, Left, Top, Bottom, Baseline</p>
<h4 id="view-base">View base</h4>
<table>
<thead>
<tr>
<th align="center">Properties</th>
<th align="center">Description</th>
</tr>
</thead>
<tbody><tr>
<td align="center">layout_toLeftOf</td>
<td align="center">뷰(View)를 기준 뷰(Anchor View)의 왼쪽(Left)에 배치</td>
</tr>
<tr>
<td align="center">layout_above</td>
<td align="center">뷰(View)를 기준 뷰(Anchor View)의 위(Above)에 배치</td>
</tr>
<tr>
<td align="center">layout_toRightOf</td>
<td align="center">뷰(View)를 기준 뷰(Anchor View)의 오른쪽(Right)에 배치</td>
</tr>
<tr>
<td align="center">layout_below</td>
<td align="center">뷰(View)를 기준 뷰(Anchor View)의 아래(Below)에 배치</td>
</tr>
<tr>
<td align="center">layout_toStartOf</td>
<td align="center">뷰(View)를 기준 뷰(Anchor View)의 시작(Start)에 배치</td>
</tr>
<tr>
<td align="center">layout_toEndOf</td>
<td align="center">뷰(View)를 기준 뷰(Anchor View)의 끝(End)에 배치</td>
</tr>
<tr>
<td align="center">layout_alignParentLeft</td>
<td align="center">뷰(View)를 부모(Parent) 영역 내에서 왼쪽(Left)에 배치</td>
</tr>
<tr>
<td align="center">layout_alignParentTop</td>
<td align="center">뷰(View)를 부모(Parent) 영역 내에서 위쪽(Top)에 배치</td>
</tr>
<tr>
<td align="center">layout_alignParentRight</td>
<td align="center">뷰(View)를 부모(Parent) 영역 내에서 오른쪽(Right)에 배치</td>
</tr>
<tr>
<td align="center">layout_alignParentBottom</td>
<td align="center">뷰(View)를 부모(Parent) 영역 내에서 아래쪽(Bottom)에 배치</td>
</tr>
<tr>
<td align="center">layout_centerHorizontal</td>
<td align="center">뷰(View)를 부모(Parent) 영역의 가로 방향 가운데 배치</td>
</tr>
<tr>
<td align="center">layout_centerVertical</td>
<td align="center">뷰(View)를 부모(Parent) 영역의 세로 방향 가운데 배치</td>
</tr>
<tr>
<td align="center">layout_centerInParent</td>
<td align="center">뷰(View)를 부모(Parent) 영역의 정 중앙(center)에 배치</td>
</tr>
<tr>
<td align="center">layout_alignParentStart</td>
<td align="center">뷰(View)를 부모(Parent) 영역의 시작 지점(Start)에 배치</td>
</tr>
<tr>
<td align="center">layout_alignParentEnd</td>
<td align="center">뷰(View)를 부모(Parent) 영역의 끝 지점(End)에 배치</td>
</tr>
<tr>
<td align="center">layout_alignLeft</td>
<td align="center">뷰(View)의 왼쪽(Left)을 기준 뷰(View)의 왼쪽(Left)에 맞춤</td>
</tr>
<tr>
<td align="center">layout_alignTop</td>
<td align="center">뷰(View)의 위(Top)를 기준 뷰(View)의 위(Top)에 맞춤</td>
</tr>
<tr>
<td align="center">layout_alignRight</td>
<td align="center">뷰(View)의 오른쪽(Right)를 기준 뷰(View)의 오른쪽(Right)에 맞춤</td>
</tr>
<tr>
<td align="center">layout_alignBottom</td>
<td align="center">뷰(View)의 아래(Bottom)를 기준 뷰(View)의 아래(Bottom)에 맞춤</td>
</tr>
<tr>
<td align="center">layout_alignBaseline</td>
<td align="center">뷰(View)의 폰트 기준선(Baseline)을 기준 뷰(View)의 폰트 기준선(Baseline)에 맞춤</td>
</tr>
</tbody></table>
<h3 id="tablelayout">TableLayout</h3>
<table>
<thead>
<tr>
<th align="center">Properties</th>
<th align="center">Description</th>
</tr>
</thead>
<tbody><tr>
<td align="center"><code>&lt;TableRow&gt;&lt;/TableRow&gt;</code></td>
<td align="center">테이블 레이아웃의 행을 추가</td>
</tr>
<tr>
<td align="center">weight</td>
<td align="center">가중치 추가</td>
</tr>
<tr>
<td align="center">layout_span=&quot;int&quot;</td>
<td align="center"><code>int</code>만큼 행 병합</td>
</tr>
</tbody></table>
<pre><code class="language-xml">&lt;TableLayout xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    xmlns:tools=&quot;http://schemas.android.com/tools&quot;
    android:layout_width=&quot;match_parent&quot;
    android:layout_height=&quot;match_parent&quot;
    tools:context=&quot;.MainActivity&quot;
    &gt;
    &lt;TableRow
    android:layout_height=&quot;0dp&quot;
    android:layout_weight=&quot;1&quot;
    android:background=&quot;#FF0000&quot;&gt;
    &lt;TextView
        android:layout_width=&quot;match_parent&quot;
        android:layout_height=&quot;match_parent&quot;
        android:text=&quot;Row1 Textview1&quot;
        android:layout_weight=&quot;1&quot;
        android:background=&quot;#ff9966&quot;
        android:layout_margin=&quot;5dp&quot;
        /&gt;
    &lt;TextView
        android:layout_width=&quot;match_parent&quot;
        android:layout_height=&quot;match_parent&quot;
        android:text=&quot;Row1 Textview2&quot;
        android:layout_weight=&quot;1&quot;
        android:background=&quot;#6600cc&quot;
        android:layout_margin=&quot;5dp&quot;
        /&gt;
    &lt;TextView
        android:layout_width=&quot;match_parent&quot;
        android:layout_height=&quot;match_parent&quot;
        android:text=&quot;Row1 Textview3&quot;
        android:layout_weight=&quot;1&quot;
        android:background=&quot;#66ffdd&quot;
        android:layout_margin=&quot;5dp&quot;
        /&gt;
    &lt;TextView
        android:layout_width=&quot;match_parent&quot;
        android:layout_height=&quot;match_parent&quot;
        android:text=&quot;Row1 Textview4&quot;
        android:layout_weight=&quot;1&quot;
        android:background=&quot;#3355cc&quot;
        android:layout_margin=&quot;5dp&quot;
        /&gt;
&lt;/TableRow&gt;
    &lt;TableRow
        android:layout_height=&quot;0dp&quot;
        android:layout_weight=&quot;1&quot;&gt;
        &lt;TextView
            android:layout_width=&quot;match_parent&quot;
            android:layout_height=&quot;match_parent&quot;
            android:text=&quot;Row2 Textview1&quot;
            android:layout_weight=&quot;1&quot;
            android:background=&quot;#99ff00&quot;
            android:layout_margin=&quot;5dp&quot;
            /&gt;
        &lt;TextView
            android:layout_width=&quot;match_parent&quot;
            android:layout_height=&quot;match_parent&quot;
            android:text=&quot;Row2 Textview2&quot;
            android:layout_weight=&quot;1&quot;
            android:background=&quot;#cc00ff&quot;
            android:layout_margin=&quot;5dp&quot;
            /&gt;
    &lt;/TableRow&gt;
&lt;/TableLayout&gt;</code></pre>
<h4 id="result-2">Result</h4>
<p><img src="https://images.velog.io/images/re_brother/post/b30c41a0-9b28-441b-8d43-59917c0dc2b2/image.png" alt=""></p>
<h3 id="constraintlayout">ConstraintLayout</h3>
<table>
<thead>
<tr>
<th align="center">Properties</th>
<th align="center">Description</th>
</tr>
</thead>
<tbody><tr>
<td align="center"><code>layout_constraintCircle=&quot;target&quot;</code></td>
<td align="center">원을 만들 대상</td>
</tr>
<tr>
<td align="center"><code>layout_constraintCircleRadius=&quot;number&quot;</code></td>
<td align="center">원의 크기</td>
</tr>
<tr>
<td align="center"><code>layout_constraintCircleAngle=&quot;radius(number)&quot;</code></td>
<td align="center">원에서 위치가 될 각도 지정</td>
</tr>
</tbody></table>
<pre><code class="language-xml">&lt;androidx.constraintlayout.widget.ConstraintLayout xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    xmlns:tools=&quot;http://schemas.android.com/tools&quot;
    android:layout_width=&quot;match_parent&quot;
    android:layout_height=&quot;match_parent&quot;
    xmlns:app=&quot;http://schemas.android.com/apk/res-auto&quot;
    tools:context=&quot;.MainActivity&quot;&gt;
    &lt;TextView
        android:id=&quot;@+id/t1&quot;
        android:layout_width=&quot;75dp&quot;
        android:layout_height=&quot;75dp&quot;
        android:text=&quot;TextView1&quot;
        app:layout_constraintLeft_toLeftOf=&quot;parent&quot;
        app:layout_constraintRight_toRightOf=&quot;parent&quot;
        app:layout_constraintTop_toTopOf=&quot;parent&quot;
        app:layout_constraintBottom_toBottomOf=&quot;parent&quot;
        android:background=&quot;#ff9966&quot;
        /&gt;
    &lt;TextView
        android:layout_width=&quot;75dp&quot;
        android:layout_height=&quot;75dp&quot;
        android:text=&quot;TextView2&quot;
        app:layout_constraintCircle=&quot;@+id/t1&quot;
        app:layout_constraintCircleRadius=&quot;150dp&quot;
        app:layout_constraintCircleAngle=&quot;140&quot;
        android:background=&quot;#66ffcc&quot;
        /&gt;
    &lt;TextView
        android:layout_width=&quot;75dp&quot;
        android:layout_height=&quot;75dp&quot;
        android:text=&quot;TextView3&quot;
        app:layout_constraintCircle=&quot;@+id/t1&quot;
        app:layout_constraintCircleRadius=&quot;150dp&quot;
        app:layout_constraintCircleAngle=&quot;190&quot;
        android:background=&quot;#99ff00&quot;
        /&gt;
    &lt;TextView
        android:layout_width=&quot;75dp&quot;
        android:layout_height=&quot;75dp&quot;
        android:text=&quot;TextView4&quot;
        app:layout_constraintLeft_toRightOf=&quot;@id/t1&quot;
        app:layout_constraintTop_toTopOf=&quot;@id/t1&quot;
        android:background=&quot;#cc00ff&quot;
        /&gt;
&lt;/androidx.constraintlayout.widget.ConstraintLayout&gt;</code></pre>
<h4 id="result-3">Result</h4>
<p><img src="https://images.velog.io/images/re_brother/post/85f8dfd7-9389-4dda-8071-118866106795/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TIL] Docker Command : December 15, 2021]]></title>
            <link>https://velog.io/@re_brother/TIL-Docker-Command-December-15-2021</link>
            <guid>https://velog.io/@re_brother/TIL-Docker-Command-December-15-2021</guid>
            <pubDate>Wed, 15 Dec 2021 07:17:47 GMT</pubDate>
            <description><![CDATA[<h2 id="docker-command">Docker Command</h2>
<blockquote>
<p><code>Docker</code> 사용빈도가 예상보다 높지 않아서 그런지 자주 쓰는 커맨드를 제외하곤 항상 구글링을 한다.
매번 검색해서 찾아서 쓰는것도 나쁘진 않지만 이번 기회에 체계적으로 정리해서 내가 보고싶을때 마다 보기 위해 이 포스팅을 작성한다.
<code>Docker</code>와 <a href="https://hub.docker.com/">Docker Hub</a>를 이용해서 <code>nginx</code> 컨테이너를 생성부터 삭제까지 중간 중간 어떤 커맨드를 사용하는지 적어본다.</p>
</blockquote>
<h3 id="intro">intro</h3>
<p><code>Docker</code>는 기본적으로 Root 권한이 필요하다. <code>Docker</code> 내부에서 Root directory에 접근할 권한이 없기 때문이다.
그래서 대부분 <code>privileged=true</code> 옵션을 사용한다고 하는데, 해당 옵션은 보안 문제가 있을 수 있으니 참고하기 바란다. <code>Docker</code>가 정상적으로 설치되었는지 확인해보자.</p>
<pre><code class="language-bash">$ sudo su
$ docker version</code></pre>
<p>혹시나 <code>Docker</code>를 설치했음에도 불구하고 위 스크립트가 정상적으로 실행되지 않는 경우에는 아래 과정을 진행하시길 바란다.
아래 과정은 현재 사용자 계정을 <code>Docker</code> 그룹에 추가하는 과정이다.</p>
<h4 id="docker-group-확인">Docker group 확인</h4>
<pre><code class="language-bash">$ cat /etc/group | grep docker
docker:x:998:</code></pre>
<p><code>Docker</code> 그룹이 생성되어있지 않다면 아래 명령어로 <code>Docker</code> 그룹을 생성한다.</p>
<pre><code class="language-bash">$ sudo groupadd docker</code></pre>
<h4 id="사용자-추가">사용자 추가</h4>
<p><code>Docker</code> 그룹에 사용자를 추가한다.</p>
<pre><code class="language-bash">$ sudo usermod -aG docker &lt;userName&gt;</code></pre>
<h4 id="docker-group-init">Docker group init</h4>
<p>현재 그룹을 <code>Docker</code> 그룹으로 변경하고 다시 버전을 확인한다.</p>
<pre><code class="language-bash">$ newgrp docker
$ docker version</code></pre>
<h3 id="docker-hub">Docker-hub</h3>
<blockquote>
<p><code>Docker-Hub</code>에는 무수히 많은 컨테이너들이 존재한다. 마이크로소프트와 같은 대형 회사에서 기본적으로 제공해주는 컨테이너, 일반 사용자들이 특수한 목적을 위해 구성해놓은 컨테이너, 개인 목적으로 업로드된 컨테이너 등등..</p>
</blockquote>
<p>이러한 컨테이너들을 어떻게 야무지게 사용하느냐에 따라서 의미없는 에너지 낭비를 피할 수 있다.
예시로 <code>nginx</code> 컨테이너 이미지를 다운로드하여 실행해보도록 하자.</p>
<pre><code class="language-bash">$ docker search
NAME                              DESCRIPTION                                     STARS     OFFICIAL   AUTOMATED
nginx                             Official build of Nginx.                        15928     [OK]
jwilder/nginx-proxy               Automated Nginx reverse proxy for docker con…   2101                 [OK]
richarvey/nginx-php-fpm           Container running Nginx + PHP-FPM capable of…   820                  [OK]
jc21/nginx-proxy-manager          Docker container for managing Nginx proxy ho…   288
linuxserver/nginx                 An Nginx container, brought to you by LinuxS…   160
tiangolo/nginx-rtmp               Docker image with Nginx using the nginx-rtmp…   147                  [OK]
...</code></pre>
<p><code>nginx</code>를 검색해본 결과 공식으로 제공되는 이미지가 있고, 그 외에 일반 사용자들이 업로드해놓은 이미지들이 있다.
공식으로 제공되는 <code>nginx</code> 이미지를 다운로드하자.</p>
<h4 id="docker-images">docker images</h4>
<pre><code class="language-bash">$ docker images
REPOSITORY    TAG       IMAGE ID       CREATED        SIZE</code></pre>
<p><code>docker images</code> 명령어를 통해 현재는 보유한 이미지를 확인할 수 있다.</p>
<h4 id="image-download-directory">image download directory</h4>
<p>다운로드된 이미지는 <code>/var/lib/docker/overlay2</code>에 저장된다.</p>
<h4 id="image-download">image download</h4>
<p>컨테이너 이미지를 다운로드 하는 커맨드는 <code>docker pull &lt;IMAGENAME&gt;:&lt;VERSION&gt;</code>이다. 버전을 생략할 경우 최신 버전으로 다운로드된다.</p>
<pre><code class="language-bash">$ docker pull nginx

Using default tag: latest
latest: Pulling from library/nginx
e5ae68f74026: Pull complete
21e0df283cd6: Pull complete
ed835de16acd: Pull complete
881ff011f1c9: Pull complete
77700c52c969: Pull complete
44be98c0fab6: Pull complete
Digest: sha256:9522864dd661dcadfd9958f9e0de192a1fdda2c162a35668ab6ac42b465f0603
Status: Downloaded newer image for nginx:latest
docker.io/library/nginx:latest</code></pre>
<p>따로 <code>&lt;VERSION&gt;</code>을 선택하지 않았기 때문에 latest 버전으로 설치된다.</p>
<pre><code class="language-bash">$ docker images

REPOSITORY   TAG       IMAGE ID       CREATED       SIZE
nginx        latest    f652ca386ed1   13 days ago   141MB

$ sudo ls -alFh /var/lib/docker/overlay2/
total 36K
drwx--x---  9 root root 4.0K Dec 16 02:02 ./
drwx--x--- 13 root root 4.0K Dec 14 00:53 ../
drwx--x---  3 root root 4.0K Dec 16 02:02 11a95abfa3e2b45cab9113eb447d98abc70b6e1a84f9435cea419c811f147b1c/
drwx--x---  4 root root 4.0K Dec 16 02:02 2d471323df269aeaff19c810a186cf36e48e3034031cbab8c03ed89e6302fb8e/
drwx--x---  4 root root 4.0K Dec 16 02:02 8235dae3e16fe4df2032d1ab2fd49c3215e173109743502715bd1f61b49c2bbe/
drwx--x---  4 root root 4.0K Dec 16 02:02 8d5040887ffef2eaeae14466a20691aaa2bb220fcc1b0e368b361ee2799b79c6/
drwx--x---  4 root root 4.0K Dec 16 02:02 9ea30971c486bc54be8175e72cb8dac86e08928a4a49dace099748bdd4da7e74/
drwx--x---  4 root root 4.0K Dec 16 02:02 c7422c3c00ac2ff7487fd66d50407f796e316436e7779fede1e3244bbcf80c1b/
drwx------  2 root root 4.0K Dec 16 02:02 l/</code></pre>
<p><code>docker images</code>, <code>sudo ls -alFh /var/lib/docker/overlay2</code>로 컨테이너 이미지 정상 다운로드를 확인한다. (<del>다운로드가 안되는 경우는 드물다. 그냥 알아두기만 하자.</del>)</p>
<hr>
<p>작성중...</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TIL] Ubuntu Docker Install : December 14, 2021]]></title>
            <link>https://velog.io/@re_brother/TIL-Ubuntu-Docker-Install-December-14-2021</link>
            <guid>https://velog.io/@re_brother/TIL-Ubuntu-Docker-Install-December-14-2021</guid>
            <pubDate>Tue, 14 Dec 2021 04:24:16 GMT</pubDate>
            <description><![CDATA[<h2 id="ubuntu-docker-install">Ubuntu Docker install</h2>
<blockquote>
<p><code>AWS EC2 Ubuntu</code> 서버에 <code>ASP.NET Core</code> 업로드 테스트를 진행중이다.
서버는 <code>Ubuntu 18.04 LTS</code>이며, 모든 작업은 CLI 환경에서 진행했다.</p>
</blockquote>
<h3 id="why-docker">Why Docker?</h3>
<blockquote>
<p><strong>도커(Docker)</strong>는 리눅스의 응용 프로그램들을 소프트웨어 컨테이너 안에 배치시키는 일을 자동화하는 오픈 소스 프로젝트이다. - 위키백과</p>
</blockquote>
<p>그렇게 어려운 말로 적혀있는건 아닌데, 모르는 사람이 들으면 이게 무슨 소리인가 싶을거다.
어느정도 현실에 빗대어 이야기하자.</p>
<p><strong>어떤 API 서버를 구축했고, 클라우드 서비스를 통해 배포 해야한다.</strong></p>
<p>이 상황에서 모든 클라우드 플랫폼이 동일한 환경을 가지고 있지 않고, 개발 당시의 환경과도 다르기 때문에 개발된 환경과 동일한 환경이 클라우드상에도 설정되어야 한다.
서비스가 정상적으로 운영되기 위한 모든 환경을 규격화하는 것이다. 이렇게 규격화된 환경은 차후 다른 서비스를 개발하거나, 유사한 서비스를 개발할 때 시간(자원)을 아껴주는 역할을 한다.</p>
<p><code>Docker</code>의 가장 큰 장점은 <strong>변경 불가능한 인프라(Immutable Infrastructure)</strong>이다. 소프트웨어는 자주 수정된다면 여러가지 오류가 발생한다. 특히 변경에 따른 이력관리도 매우 중요한데 <code>Docker</code>는 서버를 구축한 이후 변경이나 업데이트를 할 수 없다.
위와 같은 이유로 인해 <code>Docker</code>는 업데이트 개념이 아닌 서버를 통째로 바꾸는 개념이다. 서버를 업데이트 하기 위해 새로운 도커 이미지를 업데이트해서 컨테이너에 담는다.
불편하다고 생각이 들 수 있겠지만 자잘한 소프트웨어 업데이트로 인해 머리를 쥐어 뜯는것보다 변경하기 위한 이미지를 올리는 것으로 배포를 끝낼 수 있다.</p>
<h3 id="install-docker-engine-on-ubuntu">Install Docker Engine on Ubuntu</h3>
<p>아래 방법은 <code>repository</code>를 이용한 설치법이다. <code>package</code>를 이용하여 <code>Docker</code>를 설치하려면 <a href="https://docs.docker.com/engine/install/ubuntu/#install-from-a-package">Install from a package - Docker Engine</a> 를 참고하길 바란다.</p>
<h4 id="uninstall-old-versions">Uninstall old versions</h4>
<p>혹시 이전에 설치했던 <code>Docker</code>가 있을 경우를 대비하여 사전에 지워주도록 하자.</p>
<pre><code class="language-bash">$ sudo apt-get remove docker docker-engine docker.io containerd runc

Reading package lists... Done
Building dependency tree
Reading state information... Done
Package &#39;docker-engine&#39; is not installed, so not removed
Package &#39;docker&#39; is not installed, so not removed
Package &#39;containerd&#39; is not installed, so not removed
Package &#39;docker.io&#39; is not installed, so not removed
Package &#39;runc&#39; is not installed, so not removed
0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.</code></pre>
<h4 id="set-up-the-repository">Set up the repository</h4>
<ol>
<li><p><code>apt</code>를 업데이트하고, <code>https</code>를 통해 리포지토리를 쉽게 사용할 수 있도록 패키지를 설치한다.</p>
<pre><code class="language-bash">$ sudo apt-get update
$ sudo apt-get install ca-certificates curl gnupg lsb-release</code></pre>
</li>
<li><p><code>Docker</code> 공식 GPG Key를 추가한다.
```bash
$ curl -fsSL <a href="https://download.docker.com/linux/ubuntu/gpg">https://download.docker.com/linux/ubuntu/gpg</a> | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg</p>
</li>
</ol>
<p>-----BEGIN PGP PUBLIC KEY BLOCK-----</p>
<p>mQINBFit2ioBEADhWpZ8/wvZ6hUTiXOwQHXMAlaFHcPH9hAtr4F1y2+OYdbtMuth
lqqwp028AqyY+PRfVMtSYMbjuQuu5byyKR01BbqYhuS3jtqQmljZ/bJvXqnmiVXh
38UuLa+z077PxyxQhu5BbqntTPQMfiyqEiU+BKbq2WmANUKQf+1AmZY/IruOXbnq
...
YT90qFF93M3v01BbxP+EIY2/9tiIPbrd
=0YYh
-----END PGP PUBLIC KEY BLOCK-----</p>
<pre><code>
추가된 gpg key는 `/usr/share/keyrings`에 저장된다. 열어볼 필요 없다. 어차피 읽지도 못한다.

3. `stable` 리포지토리를 설정하는 경우 아래와 같이 작성하고, `nightly` or `test` 버전을 원하는 경우 `stable` 대신에 `nightly` or `test`를 넣어주면 된다.
```bash
$ echo \
  &quot;deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \
  $(lsb_release -cs) stable&quot; | sudo tee /etc/apt/sources.list.d/docker.list &gt; /dev/null</code></pre><h4 id="install-docker-engine">Install Docker Engine</h4>
<ol>
<li>위에서 설정한 리포지토리를 업데이트<pre><code class="language-bash">$ sudo apt-get update
</code></pre>
</li>
</ol>
<p>Hit:1 <a href="http://ap-northeast-2.ec2.archive.ubuntu.com/ubuntu">http://ap-northeast-2.ec2.archive.ubuntu.com/ubuntu</a> bionic InRelease
Hit:2 <a href="http://ap-northeast-2.ec2.archive.ubuntu.com/ubuntu">http://ap-northeast-2.ec2.archive.ubuntu.com/ubuntu</a> bionic-updates InRelease
Hit:3 <a href="http://ap-northeast-2.ec2.archive.ubuntu.com/ubuntu">http://ap-northeast-2.ec2.archive.ubuntu.com/ubuntu</a> bionic-backports InRelease
Get:4 <a href="https://download.docker.com/linux/ubuntu">https://download.docker.com/linux/ubuntu</a> bionic InRelease [64.4 kB]
Get:5 <a href="https://download.docker.com/linux/ubuntu">https://download.docker.com/linux/ubuntu</a> bionic/stable amd64 Packages [22.5 kB]
Get:6 <a href="https://download.docker.com/linux/ubuntu">https://download.docker.com/linux/ubuntu</a> bionic/nightly amd64 Packages [59.8 kB]
Get:7 <a href="http://security.ubuntu.com/ubuntu">http://security.ubuntu.com/ubuntu</a> bionic-security InRelease [88.7 kB]
Fetched 236 kB in 2s (153 kB/s)
Reading package lists... Done</p>
<pre><code>
2. `docker-ce`, `docker-ce-cli`, `containerd.io`를 설치한다.
```bash
$ sudo apt-get install docker-ce docker-ce-cli containerd.io
Reading package lists... Done
Building dependency tree
Reading state information... Done
...
</code></pre><p>설치가 완료되었고, <code>stable repository</code>에서 가장 최신 버전으로 설치되었다.
특정 버전을 설치해야되는 경우에는 <code>apt-cache</code> 명령어를 사용해서 원하는 버전을 설치한다.</p>
<pre><code class="language-bash">$ apt-cache madison docker-ce

 docker-ce | 5:20.10.12~3-0~ubuntu-bionic | https://download.docker.com/linux/ubuntu bionic/stable amd64 Packages
 docker-ce | 5:20.10.11~3-0~ubuntu-bionic | https://download.docker.com/linux/ubuntu bionic/stable amd64 Packages
 docker-ce | 5:20.10.10~3-0~ubuntu-bionic | https://download.docker.com/linux/ubuntu bionic/stable amd64 Packages
 ...
 docker-ce | 18.06.1~ce~3-0~ubuntu | https://download.docker.com/linux/ubuntu bionic/stable amd64 Packages
 docker-ce | 18.06.0~ce~3-0~ubuntu | https://download.docker.com/linux/ubuntu bionic/stable amd64 Packages
 docker-ce | 18.03.1~ce~3-0~ubuntu | https://download.docker.com/linux/ubuntu bionic/stable amd64 Packages

 $ sudo apt-get install docker-ce=&lt;VERSION_STRING&gt; docker-ce-cli=&lt;VERSION_STRING&gt; containerd.io</code></pre>
<p>만약 <code>5:20.10.10~3-0~ubuntu-bionic</code> 버전을 설치하고 싶은 경우</p>
<pre><code class="language-bash">$ sudo apt-get install docker-ce=5:20.10.10~3-0~ubuntu-bionic docker-ce-cli=5:20.10.10~3-0~ubuntu-bionic containerd.io</code></pre>
<p><code>&lt;VERSION_STRING&gt;</code>에 원하는 버전을 입력해서 설치하면 된다.</p>
<h4 id="test">Test</h4>
<p>동작하는지 확인은 해봐야하니까 <code>hello-world</code> 컨테이너를 실행해보자. 로컬에 <code>hello-world</code>라는 컨테이너가 없으니 <code>Docker Hub</code>에서 자동으로 컨테이너를 다운받아 실행된다.</p>
<pre><code class="language-bash">$ sudo docker run hello-world
Unable to find image &#39;hello-world:latest&#39; locally
latest: Pulling from library/hello-world
2db29710123e: Pull complete
Digest: sha256:cc15c5...4f685
Status: Downloaded newer image for hello-world:latest</code></pre>
<p>제대로 실행된건지 확인하기 위해 <code>docker ps -a</code> 명령어 실행(<code>sudo</code> 권한으로 해야함)</p>
<pre><code class="language-bash">$ sudo docker ps -a
CONTAINER ID   IMAGE         COMMAND    CREATED          STATUS                      PORTS     NAMES
e42a5f4182f1   hello-world   &quot;/hello&quot;   29 seconds ago   Exited (0) 29 seconds ago             competent_joliot
</code></pre>
<pre><code class="language-bash">$ systemctl status docker</code></pre>
<p><img src="https://images.velog.io/images/re_brother/post/6cbb6f8d-c55a-43ef-9dd4-41244a88bd98/image.png" alt=""></p>
<p>정상적으로 실행되고 있음을 확인할 수 있다.</p>
<h3 id="ending">Ending</h3>
<p>일주일에 적어도 한 번은 적어보려고 노력하는데 그게 생각처럼 쉽지가 않다.</p>
<blockquote>
<p><strong>앞으로 기록할 것들</strong></p>
</blockquote>
<ul>
<li>AWS Lambda에 업로드한 코드 수정하기(json parsing)</li>
<li>dotnet publish 패키지 증발 현상</li>
<li>AWS Lambda 함수와 AWS API Gateway 연동하기</li>
<li><del>AWS EC2 ubuntu에 Docker 환경 구축하기</del> + ASP.NET Core Docker Image</li>
</ul>
<p>꼼꼼하게 확인해가면서 배우는 습관을 들여야겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TIL] AWS Lambda .NET Core : December 9, 2021]]></title>
            <link>https://velog.io/@re_brother/TIL-AWS-Lambda-.NET-Core-December-9-2021</link>
            <guid>https://velog.io/@re_brother/TIL-AWS-Lambda-.NET-Core-December-9-2021</guid>
            <pubDate>Thu, 09 Dec 2021 06:55:08 GMT</pubDate>
            <description><![CDATA[<h2 id="aws-lambda-dockernet">AWS Lambda Docker(.NET)</h2>
<blockquote>
<p>AWS Lambda에 <code>.NET Core</code> 프로젝트를 배포하는 과정을 적어볼 예정이다.</p>
</blockquote>
<h3 id="aws-toolkit-for-visual-studio">AWS Toolkit for Visual Studio</h3>
<p>AWS 공식 문서를 참고하여 진행한다. AWS에서는 Visual Studio 2017/2019에 대한 공식 문서만 제공하고 있다.
<code>Visual Studio 2022(이하 VS2022)</code>는 아직 공식 문서가 작성되진 않았지만, <code>VS2022</code>를 지원하는 AWS Toolkit Preview는 제공되고 있으니 일단 설치해보기로 한다.</p>
<p><a href="https://aws.amazon.com/ko/visualstudio/">&gt; AWS Toolkit for Visual Studio Download</a></p>
<hr>
<h3 id="aws-toolkit-download--install">AWS Toolkit download &amp; install</h3>
<p><img src="https://images.velog.io/images/re_brother/post/7445c3ea-e7b7-4333-a173-cba61dda141a/image.png" alt="">
위에서 언급한 내용대로 <code>VS2022</code>로 설치한다.
<img src="https://images.velog.io/images/re_brother/post/662e5400-f202-47cf-9133-5b2f56966f02/image.png" alt="">
[install] 버튼 1회만 클릭하면 그리 오래걸리지 않는 시간내에 설치가 완료된다. 솔직히 카톡 PC버전 설치보다 쉬운듯.</p>
<hr>
<h3 id="basic-aws-lambda-project">Basic AWS Lambda Project</h3>
<p>설치가 완료된 상태에서 <code>VS2022</code>를 실행하게 되면 AWS 관련 템플릿들이 추가된 것을 볼 수 있다. <p style="font-size: 1px">징그럽게 많다..</p></p>
<p><img src="https://images.velog.io/images/re_brother/post/ba7efd3a-5db0-4a47-804c-ff9398551207/image.png" alt="">
<code>AWS Lambda Project (.NET Core - C#)</code>를 선택하고 프로젝트의 이름과 위치를 지정한다.</p>
<p><img src="https://images.velog.io/images/re_brother/post/4f2e1756-c3bb-4a2d-8919-b90ca7b42b77/image.png" alt="">
<strong>만들기</strong>를 클릭하면 위와 같이 Blueprint를 선택하는 창이 뜨는데 당황하지말고 <code>Empty Function</code>을 선택하면 된다.</p>
<p><img src="https://images.velog.io/images/re_brother/post/66a430d6-1a21-4877-b5a0-ce1889cf03d4/image.png" alt="">
<a href="https://console.aws.amazon.com/iamv2/home">AWS IAM 콘솔</a>에서 발급받은 Access Key와 Secret Key를 입력하고 Save 하면 된다. 혹시나 Secret Key를 분실한 경우 새로 발급받아야함.</p>
<p><a href="https://docs.aws.amazon.com/toolkit-for-visual-studio/latest/user-guide/keys-profiles-credentials.html">[참고] AWS 자격 증명 프로필 생성 문서</a></p>
<p><img src="https://images.velog.io/images/re_brother/post/29f9bd6e-a8e7-41ba-8196-7514a3e030a4/image.png" alt=""></p>
<p>정상적으로 연결되었다면 좌측에 AWS Explorer에 서비스들이 출력된다. <strong>혹시나</strong> 여기서도 AWS Explorer가 생성되지 않았다면 [보기] -&gt; [AWS Explorer] 또는 Ctrl + K, A 숏컷을 사용하면 된다.</p>
<p>솔루션 탐색기에 <code>aws-lambda-tools-defaults.json</code>, <code>Function.cs</code> 파일이 있는데 해당 파일 설명은 AWS 공식 문서를 보는게 좋을 것 같다.</p>
<blockquote>
<h3>aws-lambda-tools-defaults.json</h3><p></p>
프로젝트의 일부로 생성된 파일을 보여줍니다. Lambda 도구가 기본적으로 읽는 이 파일의 필드를 사용하여 빌드 옵션을 설정할 수 있습니다. Visual Studio의 프로젝트 템플릿에는 기본값이 있는 이러한 필드가 많이 포함되어 있습니다. function-handler 필드는 Lambda 함수가 실행될 때 실행되는 메서드를 지정합니다.
함수 핸들러를 지정하는 경우 필드는 게시 마법사에 미리 채워져 있습니다. 함수, 클래스 또는 어셈블리의 이름을 바꾸면 aws-lambda-tools-defaults.json 파일의 필드도 업데이트해야 합니다.
</blockquote>
<pre><code class="language-json">{
  &quot;Information&quot;: [
    &quot;This file provides default values for the deployment wizard inside Visual Studio and the AWS Lambda commands added to the .NET Core CLI.&quot;,
    &quot;To learn more about the Lambda commands with the .NET Core CLI execute the following command at the command line in the project root directory.&quot;,
    &quot;dotnet lambda help&quot;,
    &quot;All the command line options for the Lambda command can be specified in this file.&quot;
  ],
  &quot;profile&quot;: &quot;&quot;,
  &quot;region&quot;: &quot;&quot;,
  &quot;configuration&quot;: &quot;Release&quot;,
  &quot;framework&quot;: &quot;netcoreapp3.1&quot;,
  &quot;function-runtime&quot;: &quot;dotnetcore3.1&quot;,
  &quot;function-memory-size&quot;: 256,
  &quot;function-timeout&quot;: 30,
  &quot;function-handler&quot;: &quot;AWSLambda_basic::AWSLambda_basic.Function::FunctionHandler&quot;
}</code></pre>
<blockquote>
<h3>Function.cs</h3><p></p>
Lambda 함수로 노출할 C# 함수를 정의합니다. 이것은 FunctionHandlerLambda 함수가 실행될 때 실행되는 Lambda 기능입니다. 이 프로젝트에는 입력 텍스트 FunctionHandler를 호출하는 하나의 함수가 정의되어 ToUpper()있습니다.
</blockquote>
<pre><code class="language-cs">// Function.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

using Amazon.Lambda.Core;

// Assembly attribute to enable the Lambda function&#39;s JSON input to be converted into a .NET class.
[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))]

namespace AWSLambda_basic
{
    public class Function
    {

        /// &lt;summary&gt;
        /// A simple function that takes a string and does a ToUpper
        /// &lt;/summary&gt;
        /// &lt;param name=&quot;input&quot;&gt;&lt;/param&gt;
        /// &lt;param name=&quot;context&quot;&gt;&lt;/param&gt;
        /// &lt;returns&gt;&lt;/returns&gt;
        public string FunctionHandler(string input, ILambdaContext context)
        {
            return input?.ToUpper();
        }
    }
}
</code></pre>
<hr>
<h4 id="test">Test</h4>
<p>가장 기본적인 테스트만 진행할 계획이였기 때문에 <code>Function.cs</code>에 기본적으로 작성되어있는 <code>ToUpper()</code> 기능으로 테스트를 진행할 예정이다.</p>
<p>AWS Explorer에서 <code>AWS Lambda</code> 우클릭 후 <code>Publish to AWS Lambda</code> 선택
<img src="https://images.velog.io/images/re_brother/post/0bdf488e-4f8f-4c6d-891d-8a37e14c0e2d/image.png" alt="">
실제로 프로젝트 진행시에는 .NET Core v3.1을 사용할 계획은 없지만 이번에는 반항하지 않고 AWS 공식 문서에서 시키는데로 따라해본다.
<code>VS2019</code>, <code>VS2017</code>과는 아주 조금 다른 창이 뜨는데 엄청 많이 다른건 아니니까 요령껏 작성했다.
<img src="https://images.velog.io/images/re_brother/post/4f3768a1-e2f3-4d0b-9378-7cda98e83570/image.png" alt="">
다음 페이지에선 크게 만질만한 옵션이 없다 <code>Role Name</code>만 <code>AWSLambdaBasicExecutionRole</code>로 변경해주고 업로드한다.</p>
<p>업로드가 정상적으로 완료되면 업로드 윈도우가 자동으로 종료되고, AWS Explorer를 보면 <code>AWS Lambda</code>에 방금 추가한 Function이 추가되어있다.</p>
<h4 id="error--could-not-find-the-required-assemblydepsjson">Error : Could not find the required &#39;{Assembly}.deps.json&#39;</h4>
<pre><code class="language-json">{
  &quot;errorType&quot;: &quot;LambdaException&quot;,
  &quot;errorMessage&quot;: &quot;Could not find the required &#39;AWSLambda_basic.deps.json&#39;.
                  This file should be present at the root of the deployment package.&quot;
}
</code></pre>
<p>새롭게 생성된 Function 윈도우에서 샘플 테스트를 진행하는데 보고 따라했음에도 에러가 생긴다.
<strong>참 신기한 세상이다.</strong><p style="font-size: 1px">내 머리가 레전드인건지..</p></p>
<p>해당 오류는 <code>*.deps.json</code> 파일이 없어서 생기는 오류인데, <code>*.deps.json</code> 파일은 프로젝트를 publish하게 되면 <code>.\bin\Debug\\{runtime}\publish\</code> 디렉터리에 생성되는 파일이다.
AWS Lambda에 업로드하기 전에 Visual Studio에서 <code>dotnet publish</code> 후 파일 생성까지 확인.</p>
<hr>
<h4 id="해결">해결</h4>
<p><code>dotnet publish</code>한 프로젝트를 다시 <code>AWS Lambda</code>에 업로드했지만 해당 오류는 해결되지 않았다.
이래저래 테스트를 하던 중 <code>AWS Lambda</code> 업로드 중 <code>Rold Name</code>을 기존 <code>AWSLambdaBasicExecutionRole</code>에서 <code>AWSLambdaRole (Default policy for AWS Lambda Service Role.)</code>으로 변경했고, <code>AWS Lambda</code>에 업로드된 <code>ToUpper()</code> 함수는 정상적으로 동작했다.
<img src="https://images.velog.io/images/re_brother/post/1c7d8a6e-56a8-40e4-8f7e-199fe3ea2fbd/image.png" alt=""></p>
<hr>
<h3 id="ending">Ending</h3>
<p>한동안 기록없이 코드만 치다보니 감이 떨어진듯하다. 습관이 이렇게 무섭다.</p>
<blockquote>
<p><strong>앞으로 기록할 것들</strong></p>
</blockquote>
<ul>
<li><code>AWS Lambda</code>에 업로드한 코드 수정하기(json parsing)</li>
<li><code>dotnet publish</code> 패키지 증발 현상</li>
<li><code>AWS Lambda</code> 함수와 <code>AWS API Gateway</code> 연동하기</li>
<li><code>AWS EC2 ubuntu</code>에 <code>Docker</code> 환경 구축하기 + <code>ASP.NET Core Docker Image</code></li>
</ul>
<p>기록할 일이 많다는건 좋은 상황인지, 아닌지.</p>
<p><a href="https://devstarsj.github.io/cloud/2016/12/03/AwsLambda.CSharp/">[참고] AWS Lambda에 C# Handler 만들기 - devstarsj</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TIL] C# : December 3, 2021]]></title>
            <link>https://velog.io/@re_brother/TIL-C-December-3-2021</link>
            <guid>https://velog.io/@re_brother/TIL-C-December-3-2021</guid>
            <pubDate>Fri, 03 Dec 2021 05:45:22 GMT</pubDate>
            <description><![CDATA[<h2 id="why-c">Why C#?</h2>
<blockquote>
<p>새로운 회사에 이직하게 되었다.
자체 서비스를 C#으로 운영하고 있기에 적응해야 한다.
아마 개인적으로 스터디하는 내용을 이 게시물에 계속 추가하지 않을까 싶다.
정확하지 않은 내용이 있다면 따로 표시해놓고 수정할 계획이다.</p>
</blockquote>
<h3 id="datagrid">DataGrid</h3>
<blockquote>
<p><code>Grid</code>는 컨트롤들을 담지만 <code>DataGrid</code>는 사용자 정의 가능한 표 형태로 데이터를 표시하는 컨트롤로 행/열에 데이터를 표시하는 유연한 방법을 제공</p>
</blockquote>
<center>

<table>
<thead>
<tr>
<th>값</th>
<th align="center">범위</th>
</tr>
</thead>
<tbody><tr>
<td><code>DataGridTextColumn</code></td>
<td align="center">Text 지원</td>
</tr>
<tr>
<td><code>DataGridCheckBoxColumn</code></td>
<td align="center">CheckBox 지원</td>
</tr>
<tr>
<td><code>DataGridTemplateColumn</code></td>
<td align="center">나머지 컨트롤</td>
</tr>
</tbody></table>
</center>

<p><code>DataGridColumn</code>에 추가할 수 있는 3가지 Column이다. 많이 사용하는 <code>Text</code>와 <code>CheckBox</code> Column은 따로 지원하지만, 나머지는 <code>DataGridTemplateColumn</code>을 사용해야 한다.</p>
<p><strong>결론 : <code>Text</code>, <code>CheckBox</code>가 아닌 원하는 컨트롤을 넣고 싶을 때 <code>DataGridTemplateColumn</code>을 사용한다.</strong></p>
<p><a href="https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&amp;blogId=julymorning4&amp;logNo=222073855237">[참고] WPF DataGrid 예제 - 오라클자바커뮤니티(OJC.ASIA) // C# 관련된 포스팅이 꽤 있는편이다.
따로 시간내서 하나씩 보면 좋을듯</a></p>
<h3 id="question-mark">Question mark</h3>
<p>Python, Javascript와는 다르게 변수 타입에 <code>?</code>를 붙여 사용하는 경우가 많다.</p>
<pre><code class="language-cs">public partical class ExampleModel
{
  private int? a = null; // OK
  private int b = null; // error since b is not nullable
}</code></pre>
<p>위와 같이 C#에서 <code>int?</code>, <code>bool?</code>, <code>DateTime?</code>와 같은 <code>T?</code>의 표현은 .NET의 <code>Nullable&lt;T&gt;</code>와 같은 표현이다. <code>Nullable&lt;T&gt;</code> 구조체는 값을 가지고 있는지 체크한다.
<code>Nullable</code> 타입과 연관되어 자주 사용되는 <code>??</code> 연산자는 <code>??</code> 앞의 파라미터가 <code>NULL</code>인 경우 연산자 뒤의 값을 할당한다.</p>
<pre><code class="language-cs">double _Sum = 0;
DateTime _Time;
bool? _Selected;

public void CheckInput(int? i, double? d, DateTime? time, bool? selected)
{
    if (i.HasValue &amp;&amp; d.HasValue)
        this._Sum = (double)i.Value + (double)d.Value;

    // time valid check
    if (!time.HasValue)
        throw new ArgumentException();
    else
        this._Time = time.Value;

    // if selected is NULL &gt; false
    this._Selected = selected ?? false;
}</code></pre>
<p><strong><code>Nullable</code> 타입이 필요한 이유</strong> : SQL 서버 테이블에서 NULL을 허용하는 컬림이 있을 때, 테이블의 <code>NULL</code> 속성을 표현하기 위해서</p>
<h3 id="binding">Binding</h3>
<blockquote>
<p><code>Binding</code>은 데이터 또는 속성(프로퍼티)을 서로 연결하여 동적으로 변환 및 참조할 수 있도록 한다. <strong>한 엘리먼트의 속성</strong>을 <strong>다른 엘리먼트의 속성</strong>, 또는 <strong>데이터</strong>와 연결하는 과정
<code>Binding</code>을 통해 간단하게 서로 값들을 참조하여 동적으로 변경시킬 수 있고, 긴 처리 없이 간단하게 리스트에 <code>DataTable</code>을 표시하는 등 장점이 많기 때문에 WPF 작업시에 <code>Binding</code>은 필수적으로 사용된다.</p>
</blockquote>
<p><code>Binding</code>은 XAML측, Code-behind(C#)측에서 하는 방법이 존재한다.</p>
<h4 id="xaml-binding">XAML Binding</h4>
<pre><code class="language-xaml">&lt;!-- XAML Binding --&gt;
&lt;!-- Controll TextBox to ScrollBar --&gt;
&lt;ScrollBar Name=&quot;scrollBar&quot; Orientation=&quot;Horizontal&quot; Maximum=&quot;100&quot; LargeChange=&quot;10&quot;
    SmallChange=&quot;1&quot; Value=&quot;{Binding ElementName=textbox, Path=Text, Mode=OneWay}&quot;/&gt;
&lt;TextBox Name=&quot;textbox&quot; VerticalAlignment=&quot;Top&quot;/&gt;</code></pre>
<h4 id="code-behind-binding">Code-behind Binding</h4>
<pre><code class="language-cs">int i = 0;
private void btn0_Click(object sender, RoutedEventArgs e)
{
    i++;
    Binding binding = new Binding();
    binding.Source = slider;   // Slider
    binding.Path = new PropertyPath(&quot;Value&quot;);   // Property of Slider
    if (i % 5 == 0) binding.Mode = BindingMode.Default;
    if (i % 5 == 1) binding.Mode = BindingMode.OneTime;
    if (i % 5 == 2) binding.Mode = BindingMode.OneWay;
    if (i % 5 == 3) binding.Mode = BindingMode.OneWayToSource;
    if (i % 5 == 4) binding.Mode = BindingMode.TwoWay;
    lbl.SetBinding(ContentProperty, binding);

    lblMode.Content = binding.Mode;
}

private void btn1_Click(object sender, RoutedEventArgs e)
{
    if(lbl.Content != null) lbl.Content = (double)lbl.Content + 1;
}
</code></pre>
<h4 id="binding-mode">Binding Mode</h4>
<center>

<table>
<thead>
<tr>
<th>Mode</th>
<th align="center">의미</th>
</tr>
</thead>
<tbody><tr>
<td><code>OneTime</code></td>
<td align="center">최초 1회만 값을 전달</td>
</tr>
<tr>
<td><code>OneWay</code></td>
<td align="center">A를 B에 바인딩했다면, B의 값이 변경되면 A의 값도 변경</td>
</tr>
<tr>
<td><code>OneWayToSource</code></td>
<td align="center">OneyWay와 반대 / B를 A에 바인딩, A의 값이 변경되면 B의 값도 변경</td>
</tr>
<tr>
<td><code>TwoWay</code></td>
<td align="center">하나가 변경되면 다른 하나도 따라서 변경</td>
</tr>
</tbody></table>
</center>

<p><a href="https://blog.naver.com/alphak4000/130123447028">[참고] Binding Mode Example (XAML)</a></p>
<p><a href="https://insurang.tistory.com/342">[참고] Binding Mode Example (Code-behind)</a></p>
<h3 id="reference-site">Reference Site</h3>
<p><a href="https://chashtag.tistory.com/1">CHashtag Blog</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TIL] Docker : June 1, 2021]]></title>
            <link>https://velog.io/@re_brother/TIL-Docker-June-1-2021</link>
            <guid>https://velog.io/@re_brother/TIL-Docker-June-1-2021</guid>
            <pubDate>Tue, 01 Jun 2021 07:55:56 GMT</pubDate>
            <description><![CDATA[<h2 id="docker">Docker</h2>
<h3 id="got-permission-denied-while-trying-to-connect-to-the-docker-daemon-socket-at-">Got permission denied while trying to connect to the Docker daemon socket at ...</h3>
<pre><code class="language-shell">$ docker ps
Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Get http://%2Fvar%2Frun%2Fdocker.sock/v1.40/containers/json: dial unix /var/run/docker.sock: connect: permission denied</code></pre>
<p><code>$ docker ps</code> 명령어를 입력했을때 마주치게 되는 권한 에러로 일반적으로 도커 소켓에 접근할 권한이 없기 때문에 발생한다. 이런 경우 <code>docker ps</code> 앞에 <code>sudo</code>를 붙여주면 해결이 되지만, 사용자 계정에서도 도커에 대한 접근이 가능하게 할 수 있도록 <code>docker</code> 그룹에 사용자를 추가한다.</p>
<pre><code class="language-shell">$ sudo usermod -aG docker $USER_NAME
$ sudo su - $USER_NAME</code></pre>
<p>위와 같이 적용하게 되면 <code>sudo</code> 명령어 없이도 정상적으로 <code>docker</code> 명령어를 사용할 수 있다.</p>
<pre>
    <code>
    $ mkdir git-from-dockerfile
    $ cd git-from-dockerfile
    </code>
</pre>

<hr>
<p>작성중...</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TIL] OpenCV SystemError : May 17, 2021]]></title>
            <link>https://velog.io/@re_brother/TIL-OpenCV-SystemError-May-17-2021</link>
            <guid>https://velog.io/@re_brother/TIL-OpenCV-SystemError-May-17-2021</guid>
            <pubDate>Mon, 17 May 2021 03:51:22 GMT</pubDate>
            <description><![CDATA[<h2 id="opencv-systemerror">OpenCV SystemError</h2>
<h3 id="built-in-function-imwrite-returned-null-without-setting-an-error">built-in function imwrite returned NULL without setting an error.</h3>
<blockquote>
<p>OpenCV를 이용하여 jpg to png 변환 Process 과정을 거칠 상황이 생겨 코드를 작성하던 중 마주친 에러</p>
</blockquote>
<pre><code class="language-python">def jpg_to_png(path):
    filePath = &#39;{}&#39;.format(path)
    directory_name = basename(path)
    print(&#39;target directory :&#39;, filePath)
    fileExt = r&#39;*.jpg&#39;
    target_list = list(pathlib.Path(filePath).glob(fileExt))

    for target in range(0, len(target_list)):
        print(&#39;[INFO] target_list[{}] :&#39;.format(target), target_list[target])
        target_file = target_list[target]
        img = cv2.imread(target_file)
        cv2.imwrite(r&#39;{0}\{1}_{2}.png&#39;.format(filePath, directory_name, target), img)
        print(&#39;[COMPLETE] {0} / {1} :&#39;.format(target, len(target_list)))</code></pre>
<p><code>argparse</code>를 통해 변환 디렉토리에 대한 path를 인자로 받아 <code>path</code> 변수로 해당 경로를 전달하여 디렉토리 내에 jpg 파일을 png로 변환하는 코드이다.</p>
<pre><code class="language-python">img = cv2.imread(target_file)</code></pre>
<p>해당 부분에서 OpenCV SystemError가 발생했고, 해당 오류 수정을 위해 인터프리터 환경으로 전환하여 오류 확인을 진행하였다.
<img src="https://images.velog.io/images/re_brother/post/373c886e-6c56-450a-9687-0432ff52f9cd/image.png" alt="">일반적으로 파일의 경로는 <code>str</code> 타입으로 받아오는 경우가 많은데, 디렉토리 내에 jpg 파일 리스트를 가져오는 과정에서 사용한 <code>pathlib</code> 패키지에서 <code>WindowsPath</code> 타입으로 경로를 받아오는 현상이 발생했다.</p>
<p>의외로 타입 변환을 통해 간단하게 해결했다.</p>
<pre><code class="language-python">
img = cv2.imread(str(target_file))</code></pre>
<h3 id="결론">결론</h3>
<p>쉽게 해결할 수 있는 문제는 차근차근 쉽게 생각해서 쉽게 해결해보자.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TIL] tmux Command : February 17, 2021]]></title>
            <link>https://velog.io/@re_brother/TIL-tmux-Command-February-17-2021</link>
            <guid>https://velog.io/@re_brother/TIL-tmux-Command-February-17-2021</guid>
            <pubDate>Wed, 17 Feb 2021 09:02:37 GMT</pubDate>
            <description><![CDATA[<h1 id="tmux">tmux</h1>
<p><code>ubuntu</code> 시스템에서 종종 사용하는 <code>tmux</code>인데, 자주 사용하는 유틸리티가 아니다보니 종종 사용법을 잊는 경우가 발생하여 벨로그에 남겨봐야겠다.</p>
<h2 id="tmux-installubuntu">tmux install(ubuntu)</h2>
<p>다른 시스템에선 사용해본적이 없어서 어떻게 설치하는지는 모르겠다. 아래는 Ubuntu 기준으로 작성됨.</p>
<pre><code class="language-bash">$ apt-get install tmux</code></pre>
<h2 id="tmux-command">tmux command</h2>
<p><code>tmux</code>가 다 좋은데 커맨드가 머리에 잘 안남는다. 내가 멍청한 이유일지도 모르겠다.
아무튼 <code>tmux</code>의 커맨드는 <code>ctrl+b</code>가 기본이며, 이후 추가 입력키를 입력해서 사용하는 방식이다.</p>
<h3 id="tmux-session">tmux Session</h3>
<h4 id="create-session">create session</h4>
<pre><code class="language-bash">$ tmux new -s [name]</code></pre>
<h4 id="kill-session">kill session</h4>
<pre><code class="language-bash">$ tmux kill-session -t [name]</code></pre>
<h4 id="hide-session">hide session</h4>
<p>새롭게 생성한 세션에서 할 일을 마친 뒤 유지시키고 싶은 상태에서 기존 터미널로 나가고 싶은 경우</p>
<pre><code class="language-bash">$ ctrl + b + d</code></pre>
<h4 id="visible-session">visible session</h4>
<p><code>hide session</code>을 통해 벗어난 세션에 복귀하고 싶은 경우</p>
<pre><code class="language-bash">$ tmux a -t [name]</code></pre>
<h3 id="tmux-window">tmux window</h3>
<p><code>window</code>는 탭 정도라고 생각하면 됨.</p>
<h4 id="create-window">create window</h4>
<pre><code class="language-bash">$ ctrl + b + c</code></pre>
<h4 id="move-window">move window</h4>
<pre><code class="language-bash">$ ctrl + b + [window number]</code></pre>
<h3 id="panes">panes</h3>
<p><code>panes</code>는 화면 분할(split)에 사용된다.</p>
<h4 id="horizontal-split">horizontal split</h4>
<pre><code class="language-bash">$ ctrl + b + %</code></pre>
<h4 id="vertical-split">vertical split</h4>
<pre><code class="language-bash">$ ctrl + b + &quot;</code></pre>
<h4 id="panes-move">panes move</h4>
<p><code>panes</code>간에 커서를 이동할 때 사용</p>
<pre><code class="language-bash">$ ctrl + b + [arrow]</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TIL] Flutter Study 4 : February 9, 2021]]></title>
            <link>https://velog.io/@re_brother/TIL-Flutter-Study-4-January-13-2021</link>
            <guid>https://velog.io/@re_brother/TIL-Flutter-Study-4-January-13-2021</guid>
            <pubDate>Tue, 09 Feb 2021 10:47:23 GMT</pubDate>
            <description><![CDATA[<h2 id="flutter-study-list">Flutter Study List</h2>
<h3 id="1-text-gradient--scroll-list">1. Text Gradient / Scroll list</h3>
<h3 id="2-pushnamedandremoveuntil">2. pushNamedAndRemoveUntil</h3>
<h3 id="3-alertdialog">3. AlertDialog</h3>
<h3 id="4-automatickeepaliveclientmixin">4. AutomaticKeepAliveClientMixin</h3>
<h3 id="5-flutter-youtube">5. Flutter Youtube</h3>
<p><a href="https://www.youtube.com/channel/UCqxo_5t5-_Uhq9TfhTAat0A/videos">헤비프랜 - Heavy Fran Youtube</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TIL] Node.js System Structure : January 21, 2021]]></title>
            <link>https://velog.io/@re_brother/TIL-Node.js-System-Structure-January-21-2021</link>
            <guid>https://velog.io/@re_brother/TIL-Node.js-System-Structure-January-21-2021</guid>
            <pubDate>Thu, 21 Jan 2021 11:01:03 GMT</pubDate>
            <description><![CDATA[<h2 id="nodejs-system-structure">Node.js System Structure</h2>
<p><img src="https://images.velog.io/images/re_brother/post/b05de7e8-bccb-4f6f-91b7-73216a012028/image.png" alt=""></p>
<h3 id="concurrency--throughput">Concurrency / Throughput</h3>
<p>Node.js에서 JavaScript 실행이 싱글 스레드(이벤트 루프와 같은 스레드)에서 동작합니다. 따라서 Node.js의 동시성은 다른 작업이 완료된 후에 JavaScript 콜백 함수를 실행하는 이벤트 루프의 능력을 의미한다.
JavaScript 실행은 하나의 이벤트 루프에서만 동작하지만 이벤트 루프의 여러 콜백 함수를 실행하여 동시에 처리되도록 보이는 것을 의미한다.</p>
<p>동시에 실행되어야 하는 모든 코드는 I/O 등의 JavaScript가 아닌 작업(이벤트 루프 스레드 외 다른 스레드)이 일어나는 동안 이벤트 루프가 계속 실행될 수 있도록 해야한다.</p>
<p>이벤트 루프는 동시 작업을 다루려고 부가적인 스레드를 만드는 다른 언어의 모델과는 다르다. (<code>Node.js</code>는 스레드를 따로 생성하지 않음) =&gt; (<code>Node.js</code> 10버전으로 들어오면서 <code>Worker_thread</code>라는 모듈을 통해 스레드 생성 가능하도록 변경됐다.)</p>
<h3 id="nodejs-system">Node.js System</h3>
<p><img src="https://images.velog.io/images/re_brother/post/68f2e2e4-7778-42fe-babb-a9fc0659cf82/image.png" alt="">
<code>libuv</code>란 비동기 I/O에 집중하는 멀티 플랫폼 라이브러리이다. C언어로 개발되었으며 사실상 <code>Node.js</code>를 위해 개발된 것이다. 이 라이브러리는 다양한 I/O 폴링 메커니즘에 대한 단순한 추상화 이상의 기능을 제공한다. 어느 운영체제에서도 가능한 <code>file I/O</code>와 <code>thread</code> 기능 또한 제공된다. </p>
<p><code>libuv</code>에는 <code>thread pool</code>이 존재하는데 이 <code>thread pool</code>에 있는 <code>thread</code>가 동기적인 입출력 작업을 이벤트 루프 대신 처리를 해준다. 또한 이 <code>libuv</code>를 통해 이벤트 루프를 제어한다.</p>
<h3 id="event-loop">Event Loop</h3>
<p>이벤트 루프는 가능하다면 언제나 시스템 커널에 작업을 떠넘겨서 Node.js가 논블로킹 I/O 작업을 수행하도록 한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TIL] Flutter Study 3 : January 12, 2021]]></title>
            <link>https://velog.io/@re_brother/TIL-Flutter-Study-3-January-12-2021</link>
            <guid>https://velog.io/@re_brother/TIL-Flutter-Study-3-January-12-2021</guid>
            <pubDate>Tue, 12 Jan 2021 08:23:30 GMT</pubDate>
            <description><![CDATA[<h2 id="flutter-app-launcher-icon">Flutter App launcher icon</h2>
<h3 id="1-library">1. Library</h3>
<p>기본적으로 <code>Flutter</code> 개발 중 아이콘 변경을 진행은 가능하지만 그 과정이 어느정도의 귀찮음을 동반하기 때문에 외부 라이브러리를 활용하여 간편하게 App Launcher Icon 변경하는 방법에 대해 작성해볼까 한다.</p>
<h4 id="flutter_launcher_icons">flutter_launcher_icons</h4>
<p><img src="https://images.velog.io/images/re_brother/post/23b3f1b4-29c6-4875-a09d-52b408a1add4/image.png" alt="">
<code>flutter_launcher_icons</code> : 기존에 다소 복잡했던 아이콘 변경의 과정을 커맨드 라인으로 간편하게 아이콘 변경이 가능한 라이브러리. 아이콘 롤백이 필요한 경우 이전 런처 아이콘을 유지할 수있는 옵션 존재.
작성일 기준 2020년 8월에 발표된 <code>flutter_launcher_icons 0.8.1</code>이 LTS 버전이다.</p>
<p>라이브러리 설치는 <code>pubspec.yaml</code> 하위에 있는 <code>dev_dependencies</code>에 아래와 같이 추가하면 된다.</p>
<pre><code class="language-yaml">dev_dependencies:
        ...
        ...
        ...
    flutter_launcher_icons: ^0.8.1</code></pre>
<blockquote>
<p>외부 라이브러리 설치에 대한 설명은 아래 링크
<a href="https://velog.io/@re_brother/TIL-Flutter-Study-2-December-23-2020">https://velog.io/@re_brother/TIL-Flutter-Study-2-December-23-2020</a>
<strong>4. Client-Server Test using Node.js - Export Module</strong> 항목에 작성되어있다.</p>
</blockquote>
<h3 id="2-configure">2. Configure</h3>
<h4 id="icon">icon</h4>
<p>아이콘은 1024px * 1024px, Alpha(투명) 값이 없어야 한다.
해당 아이콘을 <code>./assets/</code> 디렉터리에 추가하고, <code>pubspec.yaml &gt; assets</code>에 <code>path</code>를 추가한다.</p>
<h4 id="flutter_launcher_iconsyaml">flutter_launcher_icons.yaml</h4>
<p><code>pubspec.yaml</code>과 동일한 디렉터리에 <code>flutter_launcher_icons.yaml</code>파일을 생성한 뒤 아래와 같이 작성한다.</p>
<pre><code class="language-yaml">flutter_icons:
  android: &quot;launcher_icon&quot;
  ios: true
  image_path: &quot;assets/icon.png&quot;</code></pre>
<p><code>image_path</code>는 실제 아이콘으로 사용할 이미지의 <code>path</code>를 입력한다.</p>
<h3 id="3-command-run">3. Command run</h3>
<p>터미널에 <code>flutter pub run flutter_launcher_icons:main</code> 입력 후 run
<img src="https://images.velog.io/images/re_brother/post/e45b810c-3014-46ab-be8d-294bb3ec0f91/image.png" alt="">
위와 같은 콘솔이 출력되었다면 정상적으로 아이콘이 변경된 것을 확인할 수 있다.</p>
<h2 id="gradle-threw-an-error-while-downloading-artifacts-from-the-network">Gradle threw an error while downloading artifacts from the network</h2>
<p><code>Flutter App</code> 테스트를 진행하던 중 <code>[!] Gradle threw an error while downloading artifacts from the network</code> 에러가 발생했다. 딱히 작업 중에 다른 파일을 수정했던건 없었는데 무슨 이유인지 앱 빌드가 되지 않아 나중을 위해 해결 방법을 작성해놓을까 한다.</p>
<h3 id="gradle-wrapperjar">gradle-wrapper.jar</h3>
<p>&gt; <code>.\flutter_app\android\gradle</code> 위치에 있는 <code>gradle-wrapper.jar</code>파일 삭제</p>
<h3 id="gradle-wrapperproperties">gradle-wrapper.properties</h3>
<ol>
<li><code>gradle-wrapper.jar</code>와 동일 디렉터리에 있는 <code>gradle-wrapper.properties</code> 파일 Open</li>
<li><pre><code class="language-c">#Fri Jun 23 08:50:38 CEST 2017
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https://services.gradle.org/distributions/gradle-5.6.2-all.zip```

</code></pre>
</li>
</ol>
<p><code>gradle-wrapper.properties</code> 파일 하단에 존재하는 <code>distributionUrl=https://.../gradle-5.6.2-all.zip</code>을 <code>https://.../gradle-6.4.1-all.zip</code>으로 변경한 뒤에 터미널에 <code>flutter run</code> 입력 후 정상적으로 컴파일이 진행되는지 확인한다.</p>
<h3 id="another-solution">Another Solution</h3>
<p>위 방법이 아닌 터미널창에 <code>flutter build</code> 명령어를 입력하면 해결된다는 글이 있는데, 해결 가능한 방법인지 아닌지는 확인을 하지 못했다. 아무튼 그런 방법도 있다고 한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TIL] Flutter Study 2 : December 23, 2020]]></title>
            <link>https://velog.io/@re_brother/TIL-Flutter-Study-2-December-23-2020</link>
            <guid>https://velog.io/@re_brother/TIL-Flutter-Study-2-December-23-2020</guid>
            <pubDate>Wed, 23 Dec 2020 09:04:20 GMT</pubDate>
            <description><![CDATA[<p><img src="https://images.velog.io/images/re_brother/post/2b756bf6-9ba9-4256-86d7-c5012e1b83b0/image.png" alt="">
<a href="https://velog.io/@re_brother/TIL-Flutter-Study-1-December-16-2020">Flutter Study 1</a>에서는 기본적인 <code>Flutter</code>의 기본적인 설명과 Install에 대한 내용을 다루었다.
이번엔 총 4가지 테스트를 진행해볼까 한다.</p>
<blockquote>
<p><strong>1. Multiple FloatingActionButton</strong>
<strong>2. Image Move</strong>
<strong>3. Button Label</strong>
<strong>4. Client-Server Test using Node.js</strong></p>
</blockquote>
<h3 id="1-multiple-floatingactionbutton">1. Multiple FloatingActionButton</h3>
<p>일반적인 <code>Flutter</code>개발 환경에서는 <code>FloatingActionButton</code>을 1개만 선언이 가능하다. 하지만 <code>Stack</code> 등을 이용하여 다중의 <code>FloatingActionButton</code>을 구현 가능하며, 테스트를 진행하던 도중 <code>Scaffold</code> 구조로 작성된 코드의 경우 정상적으로 구동이 불가능한 현상이 발생했다. 물론 <code>FloatingActionButton</code> Asset 외에도 <code>RaisedButton</code>, <code>FlatButton</code>, <code>IconButton</code> 등이 존재하지만 막연하게
&quot;<strong>다중 <code>FloatingActionButton</code>을 만들 수 있는 방법은 없을까?</strong>&quot; 라는 생각이 들었다.</p>
<h4 id="what-happened">What happened?</h4>
<p>엄청난 일이 있었던건 아니다. 앞에 말했듯이 막연한 생각으로 <code>FloatingActionButton</code>을 하나 더 만들어보고 싶었고, <code>Flutter</code>에서는 <code>FloatingActionButton</code>은 더이상 선언이 불가능하다. </p>
<h4 id="try-1">Try 1</h4>
<p>물론 아예 방법이 없진 않았다. 아래와 같이 <code>FloatingActionButton</code>을 <code>Stack</code>으로 묶어 <code>Align</code>으로 나눈 다음 선언을 한다면 한 화면 내에 여러 개의 <code>FloatingActionButton</code> 생성이 가능하다.</p>
<pre><code class="language-dart">Stack(
  children: &lt;Widget&gt;[
    Align(
      alignment: Alignment.bottomLeft,
      child: FloatingActionButton(...),
    ),
    Align(
      alignment: Alignment.bottomRight,
      child: FloatingActionButton(...),
    ),
  ],
)</code></pre>
<h4 id="try-2">Try 2</h4>
<p>현재 코드는 <code>Scaffold</code> 구조로 구성되어 있어서, 아래와 같이 <code>Stack</code> 앞에 <code>floatingActionButton</code>을 선언한다.</p>
<pre><code class="language-dart">floatingActionButton: Stack(
  children: &lt;Widget&gt;[
    Align(
      alignment: Alignment(Alignment.bottomRight.x, Alignment.bottomRight.y - 0.4),
      child: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: &#39;Increment&#39;,
        child: Icon(Icons.arrow_upward_sharp),
      ),
    ),
    Align(
      alignment: Alignment(Alignment.bottomRight.x, Alignment.bottomRight.y - 0.2),
      child: FloatingActionButton(
        onPressed: _decrementCounter,
        tooltip: &#39;Decrement&#39;,
        child: Icon(Icons.arrow_downward_sharp),
      ),
    ),
    Align(
      alignment: Alignment.bottomRight,
      child: FloatingActionButton(
        onPressed: _initCounter,
        tooltip: &#39;Initialize&#39;,
        child: Icon(Icons.album_outlined),
      )
    )
  ],
),</code></pre>
<h4 id="result">Result</h4>
<p><img src="https://images.velog.io/images/re_brother/post/83cb41d4-730c-4a16-bd9b-45ad06180716/image.png" alt="">
<code>Alignment</code>의 <code>y</code>값을 받아 각 버튼의 위치를 <code>0.2</code>, <code>0.4</code>씩 변경했다.</p>
<h3 id="2-image-move">2. Image Move</h3>
<p><code>Multiple FloatingActionButton</code>에서 여러 개의 <code>FloatingActionButton</code>을 통해 <code>counter</code> 변수의 값을 증가/감소할 수 있는 기능을 구현했다.
해당 기능을 활용하여 import된 이미지의 위치를 조정하는 기능을 구현해보자.</p>
<pre><code class="language-dart">    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: &lt;Widget&gt;[
            Container(
              //Image.asset(&#39;assets/images/test_img.png&#39;),
              color: Colors.blue,
              width: 200,
              height: 200,
            ),

            Text(
              &#39;You have pushed the button this many times:&#39;,
            ),
            Text(
              &#39;$_counter&#39;,
              style: Theme.of(context).textTheme.headline2,
            ),
          ],
        ),
      ),</code></pre>
<p>이미지를 삽입하기 전 <code>body</code>에 테스트용 <code>Container</code>를 추가하여 해당 영역 안에 이미지를 배치할 예정이다.</p>
<h4 id="import-image">Import Image</h4>
<p><img src="https://images.velog.io/images/re_brother/post/608cdc98-364d-41e8-a0a6-c9549f085cfa/image.png" alt="">
현재 프로젝트 디렉터리 구조이다. 해당 프로젝트에서 사용되는 외부 이미지와 리소스들은 모두 <code>pubspec.yaml</code>에 path를 꼭 작성해야한다.</p>
<pre><code class="language-yaml">flutter:
  # The following line ensures that the Material Icons font is
  # included with your application, so that you can use the icons in
  # the material Icons class.
  uses-material-design: true
  assets:
    - assets/images/test_img.png
    ...</code></pre>
<p>물론 1개의 파일만 추가한다면 해당 파일의 상대 경로(assets/images/test_img.png)를 추가하면 된다. 하지만 하나의 파일이 아닌 디렉터리 단위로 추가하게 될 경우 해당 디렉터리의 상대 경로(assets/images/)를 추가하면 된다.
이 때 상대 경로의 기준점은 <code>pubspec.yaml</code>이다.</p>
<p>이렇게 외부 이미지 경로에 대한 정의를 한 뒤에 <code>main.dart</code> 파일로 돌아와서 해당 이미지를 출력한다.</p>
<pre><code class="language-dart">return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: &lt;Widget&gt;[
            Container(
              color: Colors.blue,
              width: 200,
              height: 200,
            ),

            Image.asset(&#39;assets/images/test_img.png&#39;),

            Text(
              &#39;You have pushed the button this many times:&#39;,
            ),
            Text(
              &#39;$_counter&#39;,
              style: Theme.of(context).textTheme.headline2,
            ),
          ],
        ),
      ),</code></pre>
<p><code>Image.asset()</code>은 로컬에 위치한 이미지를 출력할때 사용되고 네트워크에 위치한 이미지를 Load 해야하는 경우에는 <code>Image.network()</code>를 사용한다.</p>
<h4 id="image-location">Image Location</h4>
<p>현재 테스트 프로젝트는 <code>mainAxisAlignment</code>로 각 <code>material</code>들의 <code>y</code>축이 상대적으로 고정되어있기 때문에 이미지를 <code>Container</code>에 배치한 상태로 이미지의 <code>x-Alignment Value</code>를 변경할 예정이다.</p>
<pre><code class="language-dart"> return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: &lt;Widget&gt;[
            Container(
              child: Image.asset(&#39;assets/images/test_img.png&#39;, width:200, height:200, fit: BoxFit.fill),
              padding: EdgeInsets.only(left: 2, right: 2, bottom: 1),
              alignment: Alignment(0 - (100 - _counter)/100, 0),
            ),

            // Image.asset(&#39;assets/images/test_img.png&#39;),

            Text(
              &#39;You have pushed the button this many times:&#39;,
            ),
            Text(
              &#39;$_counter&#39;,
              style: Theme.of(context).textTheme.headline2,
            ),
          ],
        ),
      ),</code></pre>
<p>위와 같이 코드를 작성하였으며, 이미지 <code>Container</code>의 <code>x</code>좌표의 위치를 변경하기 위해 해당 컨테이너의 <code>alignment</code>를 <code>Alignment(0 - (100 - _counter)/100, 0)</code>로 선언하여 <code>_counter</code>의 값이 변경될 때 마다 이미지의 위치가 변경된다.
<img src="https://images.velog.io/images/re_brother/post/4e98eb8d-266f-42d8-b2df-0376e50b2ab0/Demo_flutter.gif" alt=""></p>
<h3 id="3-button-label">3. Button Label</h3>
<p><code>Fluter Official Documents</code>를 보다가 각 버튼에 <code>label</code>이 가능하다는 사실을 알게되었고 이 <code>label</code> 기능을 추가해볼까 한다.
<img src="https://images.velog.io/images/re_brother/post/72acc462-c3e5-4522-accd-ee69a4019234/image.png" alt="">
위 이미지 처럼 기존에 정의해놓은 아이콘 우측으로 <code>label</code>을 추가하는 방식이다.</p>
<pre><code class="language-dart">      // Origin Code
      floatingActionButton: Stack(
        children: &lt;Widget&gt;[
          Align(
            alignment: Alignment(Alignment.bottomRight.x, Alignment.bottomRight.y - 0.4),
            child: FloatingActionButton(
              onPressed: _incrementCounter,
              tooltip: &#39;Increment&#39;,
              child: Icon(Icons.arrow_upward_sharp),
              backgroundColor: Colors.indigo,
            ),
          ),</code></pre>
<p>기존 <code>FloatingActionButton</code>에 바로 <code>label</code>속성을 추가하게 되면 <code>child</code>에 종속되어있는 <code>Icon</code>속성과 정상적으로 매칭되지 않아 아래와 같은 오류가 발생한다.
<img src="https://images.velog.io/images/re_brother/post/2a5d5152-fab0-4e1b-abac-f13313e876a7/image.png" alt="">
일반적인 버튼에 <code>Label</code>속성을 추가하기 위해서는 <code>FloatingActionButton</code>의 <code>extended</code> 메소드를 활용해야한다.</p>
<pre><code class="language-dart">      floatingActionButton: Stack(
        children: &lt;Widget&gt;[
          Align(
            alignment: Alignment(Alignment.bottomRight.x, Alignment.bottomRight.y - 0.4),
            child: FloatingActionButton.extended(
              onPressed: _incrementCounter,
              tooltip: &#39;Increment&#39;,
              icon: Icon(Icons.arrow_upward_sharp),
              label: Text(&#39;Increment&#39;),
              backgroundColor: Colors.indigo,
            ),
          ),</code></pre>
<p><code>FloatingActionButton.extended</code> 하위에 <code>icon</code>과 <code>label</code>을 새롭게 선언하여 조금 더 직관적인 UI를 만들 수 있다.
<img src="https://images.velog.io/images/re_brother/post/b7cbf9fd-e3a1-46d5-aefa-9decea1106ee/image.png" alt=""></p>
<h3 id="4-client-server-test-using-nodejs">4. Client-Server Test using Node.js</h3>
<p>간단하게 <code>Flutter</code> 앱 내에서 <code>Node.js</code> 서버와 통신을 진행해보려 한다.
일단 <code>Node.js</code> 서버는 요청에 대한 응답만 넘겨주는 정도로 간단하게 구현한다.</p>
<h4 id="servernodejs">Server(Node.js)</h4>
<pre><code class="language-js">
const http = require(&quot;http&quot;);

let fs = require(&#39;fs&#39;);
let bodyParser = require(&#39;body-parser&#39;);
// DB Start
const sqlite3 = require(&#39;sqlite3&#39;).verbose();
let sql = `SELECT * FROM user`;

let db = new sqlite3.Database(&#39;./app/database.db&#39;, (err) =&gt; {
        if (err) {
                return console.error(err.message);
        }

        console.log(&#39;Connected to the in local SQLite database.&#39;);
        db.all(sql, [], (err, rows) =&gt; {
                if (err) {
                        throw err;
                }
                rows.forEach((row) =&gt; {
                        console.log(row);
                });
        });

});
// DB End

http.createServer((req, res) =&gt; {
        res.statusCode = 200;
        res.setHeader(&#39;Content-Type&#39;, &#39;text/plain&#39;);
        res.end(&#39;Node.js Server-Client Test&#39;);
}).listen(4004, () =&gt; {
        console.log(&quot;Server Running at localhost:4004&quot;);
});</code></pre>
<p>간단하게 테스트용으로만 구현하려고 했는데, 마침 <code>SQLite</code> 테스트용 서버를 구축해놓은 코드가 있어서 해당 코드를 활용하여 테스트를 진행할 예정이다.
혹시나 DataBase가 필요없는 경우에는 주석 <code>DB Start</code>부터 <code>DB End</code>까지 삭제한 뒤에 진행하면 된다.</p>
<h4 id="export-module">Export Module</h4>
<p>물론 <code>http</code> 라이브러리가 기존에 설치되어 있다면 아무런 제약사항이 없겠지만 혹시나 <code>http</code> 모듈을 설치해야 한다면 <code>pubspec.yaml</code> -&gt; <code>dev_dependencies</code> 하위에 해당 모듈의 이름과 버전을 선언하고, 커맨드창에서 <code>flutter pub get</code> 명령어를 입력하면 자동으로 설치된다.</p>
<h4 id="clientflutter">Client(Flutter)</h4>
<p>서버와 통신하기 위해서는 <code>http</code> 라이브러리가 필요하기 때문에 <code>Flutter</code> 코드 최상단에 <code>http</code> 라이브러리를 참조한다.</p>
<pre><code class="language-dart">import &#39;package:http/http.dart&#39;;</code></pre>
<p><code>Node.js</code> Server - <code>flutter</code> Client 통신은 아래와 같이 진행하였으며, 이에 대한 내용은 주석으로 번호를 표시하여 설명하겠다.</p>
<pre><code class="language-dart">  @override
  _MyHomePageState createState() =&gt; _MyHomePageState();
}

class _MyHomePageState extends State&lt;MyHomePage&gt; {
  // Section 1
  String serverResponse = &#39;&#39;;

  // Section 2
  _makeGetRequest() async {
    Response response = await get( _nodeCall() );
    setState(() {
      serverResponse = response.body;
    });
  }

  // Section 3
  String _nodeCall()
  {
      return &#39;http://10.110.31.211:4004&#39;;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
          mainAxisAlignment: MainAxisAlignment.center,
          children: &lt;Widget&gt;[
            // Section 4
            Text(&#39;Server Response\n&#39;, style: Theme.of(context).textTheme.headline5),
            Text(&#39;[ &#39; + serverResponse + &#39; ]&#39;, style: Theme.of(context).textTheme.headline6),
            Container(
              child: Image.asset(&#39;assets/images/test_img.png&#39;, width:200, height:200, fit: BoxFit.fill),
              padding: EdgeInsets.only(left: 2, right: 2, bottom: 1),
              alignment: Alignment(0 - (100 - _counter)/100, 0),
            ),

            Text(
              &#39;$_counter&#39;,
              style: Theme.of(context).textTheme.headline2,
            ),
          ],
        ),
      ),

      floatingActionButton: Stack(
        children: &lt;Widget&gt;[
          Align(
            alignment: Alignment.bottomRight,
            child: FloatingActionButton.extended(
              // Section 5
              onPressed: _makeGetRequest,
              tooltip: &#39;Initialize&#39;,
              icon: Icon(Icons.album_outlined),
              label: Text(&#39;  Node.js   &#39;),
              backgroundColor: Colors.blue,
            )
          )
        ],
      ),
    );</code></pre>
<blockquote>
<p><strong>Section 1</strong> : <code>String</code> 형태의 <code>serverResponse</code>를 선언한다. 해당 변수는 최초 <code>init</code>시에 공백으로 보여지게 되며, 이는 <code>Section 2</code>에서 클라이언트 요청에 대한 서버의 <code>response</code>를 표시한다.
<strong>Section 2</strong> : <code>async</code> 방식으로 서버에 요청한다. <code>Section 3</code>에서 요청된 응답을 <code>get</code>하여 <code>response</code>에 저장한다.
<strong>Section 3</strong> : 지정해놓은 서버에 요청을 전달하여, 응답 내용을 <code>return</code>한다.
<strong>Section 4</strong> : 응답받은 <code>serverResponse</code>를 출력한다.
<strong>Section 5</strong> : <code>FloatingActionButton</code>에 <code>_makeGetRequest Function</code>을 적용한다.</p>
</blockquote>
<h3 id="5-reference-pages">5. Reference Pages</h3>
<p>구글링을 통해 여러 문제들을 해결했지만, 정리해놓고 필요할 때 마다 참고하면 좋을 블로그들</p>
<blockquote>
<p><a href="https://kyungsnim.tistory.com/74"> ▶ Flutter Widget 정리 Tistory</a>
<a href="https://pythonkim.tistory.com/110"> ▶ Flutter Image Properties 정리 Tistory</a>
<a href="https://velog.io/@wonhs717/%EB%A7%88%ED%81%AC%EB%8B%A4%EC%9A%B4Markdown-%EB%AC%B8%EB%B2%95-ytk5zemk0x"> ▶ Markdown 문법 정리 Velog</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TIL] Flutter Study 1 : December 16, 2020]]></title>
            <link>https://velog.io/@re_brother/TIL-Flutter-Study-1-December-16-2020</link>
            <guid>https://velog.io/@re_brother/TIL-Flutter-Study-1-December-16-2020</guid>
            <pubDate>Wed, 16 Dec 2020 08:31:52 GMT</pubDate>
            <description><![CDATA[<h2 id="flutter">Flutter</h2>
<h3 id="what-is-flutter">What is Flutter?</h3>
<p><img src="https://images.velog.io/images/re_brother/post/7be4d236-ebc4-4766-9918-3deb0255e675/image.png" alt="">
Flutter 는 Google 에서 만든 Cross-Platform Framework 이다.
하나의 코드 Base 로 Android / iOS 심지어 Desktop App 까지 개발 할 수 있다.
<img src="https://images.velog.io/images/re_brother/post/5a6f0f12-b513-475e-80a0-b09b5c0d08d4/image.png" alt="">
<code>Dart</code>로 작성된 하나의 코드는 각 OS에 맞게 Convert 과정을 거쳐 Build 된다.</p>
<h3 id="flutter-development-environment">Flutter Development Environment</h3>
<h4 id="install-list">Install list</h4>
<ol>
<li>Android Studio
<a href="https://developer.android.com/studio">[Link]</a></li>
<li>Android Plug-in
<code>Android Studio</code> 실행 후 <code>[Files] -&gt; [Settings]</code> 또는 <code>Ctrl + Alt + S</code> 단축키를 이용하여 <code>Plugins</code> 탭에서 <code>Flutter</code>를 설치한다.
<img src="https://images.velog.io/images/re_brother/post/974dc695-9dc1-475b-8767-c7a0679b0f5a/image.png" alt="">
<code>Flutter</code> Plugin을 설치하게 되면 <code>Dart</code> Plugin도 함께 설치된다.</li>
<li>Android Virtual-Machine
<code>[Tools] -&gt; [AVD Manager]</code> 또는 우측 상단 아이콘을 클릭하여 <code>AVD Manager</code>를 실행한다.
<img src="https://images.velog.io/images/re_brother/post/a77c9827-108f-460d-a1b4-7a2b6c1a7fbf/image.png" alt="">
<img src="https://images.velog.io/images/re_brother/post/a562a745-767c-4010-8e8e-046aed70ecaa/image.png" alt=""></li>
</ol>
<p><code>Create Virtual Device</code> 버튼을 클릭해서 새로운 안드로이드 가상 머신을 생성한다.
Android 버전은 자유롭게 선택해도 되지만, 호환 및 테스팅 이슈가 존재할 수 있으므로 Android 10 또는 11을 사용하기를 추천한다.</p>
<ol start="4">
<li>Flutter SDK
<a href="https://flutter-ko.dev/docs/get-started/install/windows">[Link]</a>
<code>Flutter SDK</code>를 다운받아 압축 해제 후 <code>flutter</code> 폴더 전체를 C드라이브 최상위 디렉터리로 이동한다.</li>
</ol>
<p><img src="https://images.velog.io/images/re_brother/post/109b96a6-7ce9-4022-83bd-d2b73fddcdfd/image.png" alt=""></p>
<p>정상적으로 폴더를 이동했다면 <code>C:\flutter\</code>에는 위와 같은 structure를 가진다.
이후 <code>SystemPropertiesAdvanced</code>를 실행하여 <code>C:\flutter\bin\</code> 경로를 환경변수 <code>PATH</code>에 추가한다.
환경변수 추가까지 정상적으로 진행했다면 명령 프롬프트(cmd)에 <code>flutter doctor</code>를 실행해본다.</p>
<p><img src="https://images.velog.io/images/re_brother/post/bad59af3-6d02-4425-9467-7a536297ba57/image.png" alt=""></p>
<p>최초 실행 시에는 위와 같은 화면이 출력된다.
<img src="https://images.velog.io/images/re_brother/post/4c9db769-4e2f-4702-8c5d-59e01d5d1705/image.png" alt=""></p>
<p>이어서 위와 유사한 커맨드가 나오게 되는데, 아마 <code>Android toolchain</code> 부분에 <code>WARNING</code>이 존재한다.
안드로이드 라이센스 관련된 내용인데 해당 부분에 대한 해결을 위해 커맨드에 <code>flutter doctor --android-licenses</code>를 입력하고,
<code>accepted (y/N)?</code> 에서 전부 <code>y</code>를 해주면 된다.</p>
<p>여기까지 진행이 완료되었다면 <code>Android Studio</code>, <code>VS Code</code>, <code>Connected device</code> 총 3개의 <code>warning</code>이 존재하는데 <code>Android Studio Warning</code>은 실제 개발 환경에 추가 Plugin을 설치했기 때문에 패스해도 된다고 한다.
<code>VS Code</code>는 개발 환경에 추가하지 않을 예정이기 때문에 패스할 것이고, <code>Connected device</code>는 현 시점에서 <code>Virtual-Machine</code>이 동작하지 않고 있기 때문에 발생하는 오류이다.</p>
<h4 id="tip">TIP?..</h4>
<p>혹시나 커맨드창에 <code>flutter doctor</code> 명령어가 정상적으로 실행되지 않고 <strong>&#39;flutter&#39;는 내/외부 명령, 실행 프로그램, 배치 파일이 아니라고</strong> 뜨는 경우에는 환경 변수가 정상적으로 등록되지 않은 경우이므로, 커맨드창에 <code>set</code> 명령어를 입력하여 환경변수를 시스템에 적용하면 된다.</p>
<h3 id="create-android-studio-flutter-project">Create Android Studio Flutter Project</h3>
<h4 id="android-studio-실행-시-관리자-권한으로-실행해주세요">Android Studio 실행 시 관리자 권한으로 실행해주세요.</h4>
<p><code>Android Studio</code>를 <strong>관리자 권한</strong>으로 실행 후 <code>[File] -&gt; [New] -&gt; [New Flutter Project]</code>를 눌러서 새 플러터 프로젝트를 생성합니다.</p>
<p><img src="https://images.velog.io/images/re_brother/post/6caa8ed3-f350-474f-8962-1c2ec45deede/image.png" alt=""></p>
<p>템플릿의 경우 <code>Flutter Application</code>을 선택하고, 다음 페이지로 넘어갑니다.
<img src="https://images.velog.io/images/re_brother/post/047ceb0f-39ec-4333-ba10-c08b922cab55/image.png" alt=""></p>
<p><code>Project Name</code>, <code>Description</code>은 자유롭게 작성하고 <code>Flutter SDK path</code>에 아까 다운로드받은 <code>Flutter SDK</code>의 <strong>절대경로</strong>를 입력한다.
제대로 안적으면 다음으로 넘어갈 수 없으니 꼭 유의해서 작성하도록 한다.
<img src="https://images.velog.io/images/re_brother/post/283abbce-c86a-4158-a0c8-f744044d1a92/image.png" alt=""></p>
<p>Android 개발만 진행한다면 <code>Include Kotlin support for Android code</code>만 체크하면 되고, 반대로  iOS 개발만 진행한다면 <code>Include Swift support for iOS code</code>만 체크하면 된다.
굳이 하나의 플랫폼만 개발할 이유는 없으므로 양쪽 다 체크하고 프로젝트 생성을 마친다.</p>
<p><img src="https://images.velog.io/images/re_brother/post/21325639-bba1-4762-9430-11cb734c6f09/image.png" alt=""></p>
<p>프로젝트 생성이 완료되면 새로운 <code>Android Studio</code>가 열리고, 좌측에 아래와 같은 파일 및 폴더들이 생성된 것을 확인할 수 있다.
<img src="https://images.velog.io/images/re_brother/post/0042da21-d513-426b-a7d9-d39552fd7fbf/image.png" alt=""></p>
<p>해당 폴더와 파일이 무엇을 의미하는지 간략하게 설명하고 마무리할까 한다.</p>
<blockquote>
</blockquote>
<p><code>android</code> – Android Application을 만들기 위해 자동으로 생성된 소스코드
<code>ios</code> – iOS Application을 만들기 위해 자동으로 생성된 소스코드
<code>lib</code> – <code>Flutter Framework</code>를 사용하여 쓰여진 다트 코드를 담고 있는 메인 폴더
<code>lib/main.dart</code> – <code>Flutter Application</code>의 엔트리 포인트
<code>test</code> – <code>Flutter Application</code>을 테스트하기 위한 <code>Dart</code> 코드를 담고 있는 폴더
<code>test/widget_test.dart</code> – Sample Code
<code>.gitignore</code> – Git에 들어가지 않을 파일들 설정
<code>.metadata</code> – <code>Flutter</code> 툴에 의해 자동 생성됨
<code>.packages</code> – <code>Flutter</code> 패키지를 추적하기 위해 자동 생성됨
<code>.iml</code> – Android Studio에 의해 사용되는 프로젝트 파일
<code>pubspec.yaml</code> – Pub에 의해 사용되는 Flutter Package Manager</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TIL] Chart.js Mouse Over Error : November 20, 2020]]></title>
            <link>https://velog.io/@re_brother/TIL-Chart.js-Mouse-Over-Error-November-20-2020</link>
            <guid>https://velog.io/@re_brother/TIL-Chart.js-Mouse-Over-Error-November-20-2020</guid>
            <pubDate>Fri, 20 Nov 2020 06:40:51 GMT</pubDate>
            <description><![CDATA[<h2 id="mouse-over-error">Mouse Over Error</h2>
<p><code>Chart.js</code>를 이용하여 데이터 차트 생성 후에 데이터를 수정할 경우 정상적으로 출력되는 것으로 보이지만 이전 데이터가 있던 위치에 마우스 오버를 하면 이전 데이터가 출력되는 현상이 있다.
<img src="https://images.velog.io/images/re_brother/post/3d03624a-eee1-4af7-a40c-1747b3c29123/image.png" alt="">
나만 겪고 있는 현상은 아니였고, 이를 해결하기 위해 삽질을 시작해봐야겠다.</p>
<h3 id="try">Try</h3>
<p>데이터 차트 <code>chart0</code>과 <code>chart0_1</code>이 존재할 경우 해당 차트를 <code>Destroy()</code>하는 방법을 적용해보자.</p>
<h4 id="code">Code</h4>
<pre><code class="language-js">function chart_search_total(grade1, grade2, grade3, grade4, grade5, spend1, spend2, spend3, spend4, spend5)
{
    console.log(chart0);
    console.log(chart0_1);

    if((chart0 != undefined) &amp;&amp; (chart0_1 != undefined))
    {
        chart0.destroy();
        chart0_1.destroy();
    }

    var ctx0 = document.getElementById(&#39;totalChart&#39;).getContext(&#39;2d&#39;);
    var ctx0_1 = document.getElementById(&#39;totalChart_1&#39;).getContext(&#39;2d&#39;);

    var data0 = {
        // The type of chart we want to create
        title: &quot;Monthly Grade Variation&quot;,
        type: &#39;line&#39;,
        // The data for our dataset
        data: {
            labels: [&quot;April&quot;, &quot;May&quot;, &quot;June&quot;, &quot;July&quot;, &quot;August&quot;],
            datasets:
            [
                {
                    label: &quot;Search&quot;,
                    backgroundColor: &#39;rgb(153, 0, 153)&#39;,
                    fill:false,
                    borderColor: &#39;rgb(153, 0, 153)&#39;,
                    lineTension:0.25,
                    data: [grade1, grade2, grade3, grade4, grade5],
                },
            ]
        },
        // Configuration options go here
        options: {
            title: {
                display: &#39;ture&#39;,
                text: &#39;Monthly Grade Variation&#39;,
                fontSize: 20
            },
            //responsive: false,
            scales: {
                yAxes: [{
                    ticks: {
                        reverse: true,
                        min: 1,
                        max: 9
                    }
                }]
            }
        }
    }

    var data0_1 = {
        // The type of chart we want to create
        title: &quot;Monthly Spending Variation&quot;,
        type: &#39;bar&#39;,
        // The data for our dataset
        data: {
            labels: [&quot;April&quot;, &quot;May&quot;, &quot;June&quot;, &quot;July&quot;, &quot;August&quot;],
            datasets:
            [
                {
                    label: &quot;Spending&quot;,
                    backgroundColor: &#39;rgb(204, 102, 204)&#39;,
                    fill:false,
                    borderColor: &#39;rgb(204, 102, 204)&#39;,
                    lineTension:0.25,
                    data: [spend1, spend2, spend3, spend4, spend5],
                }
            ]
        },
        // Configuration options go here
        options: {
            title: {
                display: &#39;ture&#39;,
                text: &#39;Monthly Spending Variation&#39;,
                fontSize: 20
            },
        }
    }

    var chart0 = new Chart(ctx0, data0);
    var chart0_1 = new Chart(ctx0_1, data0_1);

}</code></pre>
<p>무엇이 문제인지 정상적으로 동작하지 않는다. 왜 <code>console.log()</code>를 해봐도 <code>chart0</code>와 <code>chart0_1</code>의 내용이 <code>undefined</code>로 표현되는걸까</p>
<h3 id="solution">Solution</h3>
<p><strong>답은 가까이 있었다.</strong>
<code>chart_search_total</code> 함수 내에 <code>chart0</code>, <code>chart0_1</code> 변수를 매번 새로 선언하니 당연히 다음 Chart가 생성될 때 마다 해당 변수가 새로 생성되는 것이다.
이를 해결하기 위해 <code>chart0</code>, <code>chart0_1</code> 변수를 전역 변수로 선언하고, 다음 차트를 생성할 때는 해당 전역 변수를 참조하여 차트를 생성한다.</p>
<h4 id="code-1">Code</h4>
<pre><code class="language-js">var chart0;
var chart0_1;

function chart_search_total(grade1, grade2, grade3, grade4, grade5, spend1, spend2, spend3, spend4, spend5)
{
    console.log(chart0);
    console.log(chart0_1);

    if((chart0 != undefined) &amp;&amp; (chart0_1 != undefined))
    {
        chart0.destroy();
        chart0_1.destroy();
    }

    var ctx0 = document.getElementById(&#39;totalChart&#39;).getContext(&#39;2d&#39;);
    var ctx0_1 = document.getElementById(&#39;totalChart_1&#39;).getContext(&#39;2d&#39;);

    var data0 = {
        // The type of chart we want to create
        title: &quot;Monthly Grade Variation&quot;,
        type: &#39;line&#39;,
        // The data for our dataset
        data: {
            labels: [&quot;April&quot;, &quot;May&quot;, &quot;June&quot;, &quot;July&quot;, &quot;August&quot;],
            datasets:
            [
                {
                    label: &quot;Search&quot;,
                    backgroundColor: &#39;rgb(153, 0, 153)&#39;,
                    fill:false,
                    borderColor: &#39;rgb(153, 0, 153)&#39;,
                    lineTension:0.25,
                    data: [grade1, grade2, grade3, grade4, grade5],
                },
            ]
        },
        // Configuration options go here
        options: {
            title: {
                display: &#39;ture&#39;,
                text: &#39;Monthly Grade Variation&#39;,
                fontSize: 20
            },
            //responsive: false,
            scales: {
                yAxes: [{
                    ticks: {
                        reverse: true,
                        min: 1,
                        max: 9
                    }
                }]
            }
        }
    }

    var data0_1 = {
        // The type of chart we want to create
        title: &quot;Monthly Spending Variation&quot;,
        type: &#39;bar&#39;,
        // The data for our dataset
        data: {
            labels: [&quot;April&quot;, &quot;May&quot;, &quot;June&quot;, &quot;July&quot;, &quot;August&quot;],
            datasets:
            [
                {
                    label: &quot;Spending&quot;,
                    backgroundColor: &#39;rgb(204, 102, 204)&#39;,
                    fill:false,
                    borderColor: &#39;rgb(204, 102, 204)&#39;,
                    lineTension:0.25,
                    data: [spend1, spend2, spend3, spend4, spend5],
                }
            ]
        },
        // Configuration options go here
        options: {
            title: {
                display: &#39;ture&#39;,
                text: &#39;Monthly Spending Variation&#39;,
                fontSize: 20
            },
        }
    }

    chart0 = new Chart(ctx0, data0);
    chart0_1 = new Chart(ctx0_1, data0_1);

}</code></pre>
<h3 id="end">End</h3>
<p>간단하지만 어렵고 멍청한 접근으로 일을 힘들게 하는 것 같다.
머리좀 써야겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TIL] C# WPF : October 19, 2020]]></title>
            <link>https://velog.io/@re_brother/TIL-C-WPF-October-19-2020</link>
            <guid>https://velog.io/@re_brother/TIL-C-WPF-October-19-2020</guid>
            <pubDate>Wed, 11 Nov 2020 00:57:14 GMT</pubDate>
            <description><![CDATA[<h1 id="c-wpf">C# WPF</h1>
<h2 id="file-load">File Load</h2>
<h3 id="not-enough-data">Not enough data</h3>
<p><code>lastFile_Rename</code> Function은 지난번에 작성을 완료했고, 이름이 변경된 해당 엑셀 파일을 WPF Windows 상에 Load하여 출력하는 기능을 구현해야한다.
나름 자료가 없진 않아보이는데 Simple 해보이는 패키지는 온통 유료 패키지고, 무료 패키지는 여러가지로 지저분하게 엮여있는 자료만 존재한다.
하나 하나 적용해보면서 에러를 맞아보자</p>
<h3 id="try-1--wpf-excel-datagrid-import">Try 1 : <a href="https://nomadcoder.tistory.com/entry/WPF-Excel-%ED%8C%8C%EC%9D%BC-%EC%9D%BD%EC%96%B4%EC%84%9C-Datagrid-%EC%97%90-%EB%84%A3%EA%B8%B0-Excel-Import">WPF Excel Datagrid import</a></h3>
<p>Visual Stduio 2015 에서 기본으로 제공되는 WPF Datagrid Tool에 엑셀 데이터를 Load/Save 하는 방식이다.
<code>EPPlus</code> 패키지 내의 <code>ExcelPackage</code>를 사용한다.</p>
<pre><code>&lt;!-- Window_taxdata_01.xaml --&gt;

&lt;DataGrid x:Name=&quot;dataGrid_scrap_01&quot;
            HorizontalAlignment=&quot;Left&quot;
            Margin=&quot;70,255,350,0&quot;
            VerticalAlignment=&quot;Top&quot;
            Height=&quot;auto&quot;
            Width=&quot;auto&quot;
            CanUserAddRows=&quot;False&quot;
            CanUserDeleteRows=&quot;False&quot;
            AutoGenerateColumns=&quot;False&quot;
            ItemsSource=&quot;{Binding ObserList}&quot;&gt;
            &lt;DataGrid.Resources&gt;
                &lt;Style TargetType=&quot;{x:Type DataGridColumnHeader}&quot;&gt;
                    &lt;Setter Property=&quot;Background&quot; Value=&quot;White&quot;/&gt;
                    &lt;Setter Property=&quot;FontWeight&quot; Value=&quot;SemiBold&quot;/&gt;
                    &lt;Setter Property=&quot;BorderThickness&quot; Value=&quot;0,0,1,2&quot;/&gt;
                    &lt;Setter Property=&quot;BorderBrush&quot; Value=&quot;Black&quot;/&gt;
                    &lt;Setter Property=&quot;Foreground&quot; Value=&quot;Black&quot;/&gt;
                    &lt;Setter Property=&quot;HorizontalContentAlignment&quot; Value=&quot;Center&quot;/&gt;
                &lt;/Style&gt;
            &lt;/DataGrid.Resources&gt;
            &lt;DataGrid.CellStyle&gt;
                &lt;Style TargetType=&quot;DataGridCell&quot;&gt;
                    &lt;Setter Property=&quot;TextBlock.TextAlignment&quot; Value=&quot;Center&quot;/&gt;
                    &lt;Setter Property=&quot;IsEditing&quot; Value=&quot;True&quot;/&gt;
                &lt;/Style&gt;
            &lt;/DataGrid.CellStyle&gt;
            &lt;DataGrid.Columns&gt;
                &lt;DataGridTextColumn Header=&quot;Month&quot; Width=&quot;1*&quot; Binding=&quot;{Binding DataMonth}&quot; IsReadOnly=&quot;True&quot;/&gt;
                &lt;DataGridTextColumn Header=&quot;Division&quot; Width=&quot;1.5*&quot; Binding=&quot;{Binding DataDivision}&quot; IsReadOnly=&quot;True&quot;/&gt;
                &lt;DataGridTextColumn Header=&quot;고지금액&quot; Width=&quot;3*&quot; Binding=&quot;{Binding DataCharge1}&quot; IsReadOnly=&quot;True&quot;/&gt;
                &lt;DataGridTextColumn Header=&quot;수납금액&quot; Width=&quot;3*&quot; Binding=&quot;{Binding DataCharge2}&quot; IsReadOnly=&quot;True&quot;/&gt;
                &lt;DataGridTextColumn Header=&quot;미납금액&quot; Width=&quot;3*&quot; Binding=&quot;{Binding DataCharge3}&quot; IsReadOnly=&quot;True&quot;/&gt;
            &lt;/DataGrid.Columns&gt;
        &lt;/DataGrid&gt;</code></pre><p><code>Nuget Package Manager</code>을 통해 <code>EPPlus</code>를 설치하고 <code>*.cs</code> 코드에 아래 내용을 추가한다.</p>
<pre><code>using OfficeOpenXml;

// ...

public void excelFile_Load(string rename_file)
{
   var fi = new FileInfo(rename_file);
   using (var package = new ExcelPackage(fi))
   {
      // ...
   }
</code></pre><p><del>뭔가 중간에 어려움이 있긴 했지만 1트만에 성공할 것 같은 분위기다.</del></p>
<h3 id="try-2--microsoftofficeinteropexcel">Try 2 : Microsoft.Office.Interop.Excel</h3>
<p>생성해놓은 <code>DataGrid</code>는 유지하고 <code>Microsoft.Office.Interop.Excel</code> 패키지를 통해 엑셀 데이터를 Load해서 2차원 배열에 변수들을 담아 <code>DataGrid</code>에 옮겨주는 식으로 진행해볼까 한다.
<img src="https://images.velog.io/images/re_brother/post/fa87e345-f0e4-4b20-badd-9e901a3c86c0/image.png" alt="">
<code>NuGet Package Manager</code>에서 <code>Microsoft.Office.Interop.Excel</code> 패키지를 install 한다.</p>
<pre><code>using Microsoft.Office.interop.Excel;

// ...

public void excelFile_Load(string rename_file)
{
   Application application = new Application();
   Workbook workbook = application.Workbooks.Open(Filename: rename_file);</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[TIL] C# WPF : October 14, 2020]]></title>
            <link>https://velog.io/@re_brother/TIL-C-WPF-October-14-2020</link>
            <guid>https://velog.io/@re_brother/TIL-C-WPF-October-14-2020</guid>
            <pubDate>Thu, 15 Oct 2020 04:42:32 GMT</pubDate>
            <description><![CDATA[<h1 id="c-wpf">C# WPF</h1>
<h2 id="file-download--rename">File download &amp; rename</h2>
<p><code>Selenium</code> 패키지를 업무 자동화 시스템 개발에 사용중인데, 데이터 시트를 다운받는 과정에서 데이터 시트 구분을 위해 파일명 변경이 필요하다.
다운로드 받은 파일을 자체적으로 Rename해주는 기능은 <code>Selenium</code>에서 지원되지 않는 것으로 보이며, 해당 이슈는 자체적으로 처리해야겠다.
데이터가 저장되는 Default directory를 Fix한 상태로 해당 directory에서 가장 마지막에 수정된 파일의 이름을 변경해볼까 한다.
File Search에는 <code>System.IO</code> 패키지를 사용한다.</p>
<pre><code class="language-cs">public void lastFile_search(string title)
{
    // init part
    var files = new DirectoryInfo(driver_download_path).GetFiles(&quot;*.xls&quot;);
    string latestFile = &quot;&quot;;
    DateTime lastUpdated = DateTime.MinValue;

    // search part
    foreach (FileInfo file in files)
    {
        if (file.LastWriteTime &gt; lastUpdated)
        {
            lastUpdated = file.LastWriteTime;
            latestFile = file.Name;
        }
        // log part
        Console.WriteLine(&quot;Last File is &quot; + latestFile);
    }
 }</code></pre>
<h3 id="error">Error</h3>
<p>마지막에 생성된 파일만 logging하는 것이 아니라 모든 파일을 logging하는 상황이 벌어졌다.</p>
<h3 id="solution">Solution</h3>
<p><code>foreach</code>문 내에 <code>Console.WriteLine</code>를 사용해서 벌어진 일이다.</p>
<pre><code class="language-cs">    foreach (FileInfo file in files)
    {
        if (file.LastWriteTime &gt; lastUpdated)
        {
            lastUpdated = file.LastWriteTime;
            latestFile = file.Name;
        }
    }
    // log part
    Console.WriteLine(&quot;Last File is &quot; + latestFile);</code></pre>
<p>위와 같이 <code>Console.WriteLine</code>를 <code>foreach</code>문 밖으로 꺼냈더니 문제없이 실행된다.</p>
<h3 id="next">Next</h3>
<p>마지막에 생성된 파일을 찾았으니 해당 파일을 수정하는 Function만 정의를 하면 완성이다.
File Search에 사용했던 <code>System.IO</code> 패키지의 하위 패키지인 <code>System.IO.File.Move</code> 패키지를 사용하여 파일명을 직접적으로 바꾸는 것이 아닌 파일 이동을 통해 이름을 변경한다.
파일 이동을 위해서는 해당 파일의 절대 경로가 필요하다. 이미 파일의 절대 경로는 <code>driver_download_path</code>로 지정해놓았기에 작업에 귀찮은 점은 많이 덜었다.</p>
<h2 id="c-latest-file-search--rename-code">C# Latest File Search &amp; Rename Code</h2>
<pre><code class="language-cs">public void lastFile_Rename(string title)
{
    //string Folder = driver_download_path;
    var files = new DirectoryInfo(driver_download_path).GetFiles(&quot;*.xls&quot;);
    string latestFile = &quot;&quot;;

    DateTime currentTime = DateTime.Now;

    // for rename_file
    string file_yyyymmdd = currentTime.ToString(&quot;yyyyMMdd&quot;);
    string file_hhmmss = currentTime.ToString(&quot;HHmmss&quot;);
    string rename_file = driver_download_path + title + &quot;_&quot; + file_yyyymmdd + &quot;_&quot; + file_hhmmss + &quot;.xls&quot;;

    DateTime lastUpdated = DateTime.MinValue;

    // search latest file name
    foreach (FileInfo file in files)
    {
        if (file.LastWriteTime &gt; lastUpdated)
        {
            lastUpdated = file.LastWriteTime;
            latestFile = file.Name;
        }
    }

    // set a target file path
    string origin_file = driver_download_path + latestFile;

    // launch rename
    System.IO.File.Move(origin_file, rename_file);

    //Console.WriteLine(&quot;Test : &quot; + latestFile);
    //Console.WriteLine(title);
}</code></pre>
<h3 id="values-in-code">Values in Code</h3>
<p><code>string file_yyyymmdd</code> : 파일 이름 구분을 위해 현재 날짜를 yyyy-mm-dd 식으로 지정
<code>string file_hhmmss</code> : 파일 이름 구분을 위해 현재 시간을 hh-mm-ss 식으로 지정
<code>string rename_file</code> : 최종적으로 바꿔지는 파일 이름과 절대경로
<code>string origin_file</code> : 이름을 바꿀 파일의 이름과 절대경로</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TIL] C# Windows Presentation Foundation : October 7, 2020]]></title>
            <link>https://velog.io/@re_brother/TIL-C-Windows-Presentation-Foundation</link>
            <guid>https://velog.io/@re_brother/TIL-C-Windows-Presentation-Foundation</guid>
            <pubDate>Tue, 06 Oct 2020 07:59:01 GMT</pubDate>
            <description><![CDATA[<h2 id="windows-presentation-foundation">Windows Presentation Foundation</h2>
<h3 id="c-wpf">C# WPF</h3>
<p><code>Microsoft</code>에서 <code>Java</code>이겨보겠다고 부랴부랴 만들어낸 C#으로 Windows GUI 프로그램 개발을 진행하게 되었다.
<code>Python</code>에서 <code>pip</code>으로 제공되어 설치해서 사용했던 <code>selenium</code> 패키지가 C#에서도 <code>Nuget Package Manager</code>를 통해 설치하여 사용이 가능하고, 이를 통하여 특수 업무 자동화 프로젝트 개발을 진행한다.</p>
<p>이전에도 Windows C++ MFC 개발을 진행한 경험은 있으나, MFC 개발과 WPF 개발을 경험한 입장에서 몇가지 차이점을 끄적여볼까 한다.</p>
<h3 id="c-mfc-vs-c-wpf">C++ MFC vs C# WPF</h3>
<h4 id="공통점">공통점</h4>
<p>크게 언어가 다른 점과 사소한 몇가지를 제외하면 비슷한 느낌이 많이 든다. 개발하는 툴도 마찬가지로 <code>Microsoft</code>사의 <code>Visual Studio</code>를 사용하고, MFC에 있었던 레이아웃(Toolbox)이 WPF에도 그대로 전승되어 사용된다는 느낌이 많았고, 해당 Tool들의 Rule이 매우 흡사하다.</p>
<h4 id="차이점">차이점</h4>
<ol>
<li>생각
나는 평생 C#이라는 언어를 쓸 것이라는 생각을 해본 경험조차 없다.
상당한 거부감이 있었고 도대체 XAML은 뭔지도 모르겠고, 그냥 C++로 MFC 개발 진행할 때도 자료가 매우 없다는 점을 느꼈는데 도대체 써본적도 없는 C#으로 어떻게 개발을 진행해야할지 갈피 조차도 잡히지 않았다.
하지만 생각과는 다르게 2년 6개월전 MFC를 처음 개발했던 나와 지금 현재의 나 사이에 차이점을 느낄 수 있었다.</li>
<li>패키지
<code>C++ MFC</code> 개발 진행시에는 다른 사람이 구현해놓은 패키지 적용을 위해선 해당 패키지의 <code>*.cpp</code> 파일과 <code>*.h</code> 파일을 끌어와서 프로젝트에 적용하고 해당 패키지를 사용할 파일에서 하나 하나씩 선언 후에 사용이 가능했다.
<code>C# WPF</code>에서는 <code>Nuget Package Manager</code>를 통해 패키지를 바로 검색하고, 프로젝트에 <code>import</code>하여 기타 다른 작업 없이 사용할 수 있다. 개인적으로 <code>Python</code>의 <code>pip</code>보다 편하다고 생각된다.</li>
<li>XAML(eXtensible Application Markup Language)
<code>C++ MFC</code>에서 Toolbox에 있는 각종 레이아웃들을 끄집어내어 다이얼로그에 배치했다면, <code>WPF</code>에서는 HTML과 유사한 Markup Language를 통해 레이아웃 생성이 가능하다. script를 통한 event 발생과 레이아웃 setting이 가능하며, 초반에 익숙해지기 어려울 뿐이지 익숙해진다면 충분히 매력있는 요소 중 하나라고 생각한다.</li>
</ol>
<h3 id="to-be">to be</h3>
<p>아마 한동안은 회사 업무로 인해 <code>Python</code>, <code>Javascript</code>보다 <code>C#</code>을 쓰는 일이 더 잦아질 것 같다. 또한 <code>Flutter</code> 개발을 위해 <code>Dart</code>도 쓰게 될 것 같은데...
빠르게 마무리하고, 대학원 진학 준비도 하고... 마지막 학기도 잘 마무리해야겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TIL] VSCode Extension : July 10, 2020]]></title>
            <link>https://velog.io/@re_brother/TIL-VSCode-Extension-July-10-2020</link>
            <guid>https://velog.io/@re_brother/TIL-VSCode-Extension-July-10-2020</guid>
            <pubDate>Fri, 10 Jul 2020 02:15:27 GMT</pubDate>
            <description><![CDATA[<h3 id="vscode">VSCode</h3>
<p>Javascript 개발자를 위한 VSCode Extension</p>
<p><a href="https://velog.io/@cada/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EA%B0%9C%EB%B0%9C%EC%9E%90%EA%B0%80-%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94-VSCode-Extension-1%ED%83%84">VSCode Extension for Javascript Developer[1]</a></p>
<p><a href="https://velog.io/@cada/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EA%B0%9C%EB%B0%9C%EC%9E%90%EA%B0%80-%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94-VSCode-Extension-2%ED%83%84">VSCode Extension for Javascript Developer[2]</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TIL] Ubuntu Settings : July 8, 2020]]></title>
            <link>https://velog.io/@re_brother/TIL-Ubuntu-Settings-July-8-2020</link>
            <guid>https://velog.io/@re_brother/TIL-Ubuntu-Settings-July-8-2020</guid>
            <pubDate>Fri, 10 Jul 2020 00:50:01 GMT</pubDate>
            <description><![CDATA[<h2 id="ubuntu-install">Ubuntu install</h2>
<h3 id="ubuntu-os-install">Ubuntu OS install</h3>
<p>혹시나 Ubuntu 설치법을 모르는 경우는 아래를 참고하시어 설치를 진행하시길 바람.</p>
<blockquote>
<p><a href="https://blog.lael.be/post/7541">Ubuntu 18.04 LTS 운영체제 설치하는 방법 @Lael&#39;s World</a></p>
</blockquote>
<h3 id="change-apt-source">Change apt source</h3>
<ul>
<li>다운로드 속도 향상을 위해 repository mirror를 카카오 mirror로 변경</li>
<li>/etc/apt/sources.list에 기본 ubuntu mirror 주소가 있고, 이를 vi 명령어를 이용하여 변경<pre><code class="language-bash">~$ sudo vi /etc/apt/sources.list</code></pre>
<pre><code>:%s/kr.archive.ubuntu.com/mirror.kakao.com</code></pre><pre><code class="language-bash">~$ sudo apt-get update</code></pre>
<h3 id="check-network-interface">Check Network Interface</h3>
Network Adaptor 확인을 위해 <code>net-tools</code>에 포함된 <code>ifconfig</code>를 사용한다. 기본 Ubuntu 18.04 LTS는 포함하고 있지 않으므로, <code>net-tools</code>를 설치한다.
정상적으로 설치가 되었다면 <code>ifconfig -a</code> 로 본인의 네트워크 인터페이스를 확인한다.<pre><code class="language-bash">~$ sudo apt install net-tools
~$ ifconfig -a
enp2s0: flags=4163&lt;UP,BROADCAST,RUNNING,MULTICAST&gt;  mtu 1500
      inet 192.168.197.218  netmask 255.255.255.0  broadcast 192.168.197.255
      inet6 fe80::7393:4006:7019:bb66  prefixlen 64  scopeid 0x20&lt;link&gt;
      inet6 fd44:c6a6:922c:0:79c6:764a:444d:b95e  prefixlen 64  scopeid 0x0&lt;global&gt;
      inet6 fd44:c6a6:922c:0:f0dc:7b5b:e40c:23d2  prefixlen 64  scopeid 0x0&lt;global&gt;
      inet6 fd44:c6a6:922c::489  prefixlen 128  scopeid 0x0&lt;global&gt;
      ether b4:2e:99:04:10:e1  txqueuelen 1000  (Ethernet)
      RX packets 3946410  bytes 550270004 (550.2 MB)
      RX errors 0  dropped 254766  overruns 0  frame 0
      TX packets 114759  bytes 8209523 (8.2 MB)
      TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
</code></pre>
</li>
</ul>
<p>lo: flags=73&lt;UP,LOOPBACK,RUNNING&gt;  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        inet6 ::1  prefixlen 128  scopeid 0x10<host>
        loop  txqueuelen 1000  (Local Loopback)
        RX packets 1623  bytes 149090 (149.0 KB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 1623  bytes 149090 (149.0 KB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0</p>
<pre><code>해당 Ubuntu의 Network Interface는 ``enp2s0``인 것을 확인하였으니, 고정 IP 설정을 진행한다.

### Setting a Static IP Address
네트워크 config 변경을 위해 ``network-manager-all.yaml`` 을 열고
</code></pre><p>~$ sudo vi /etc/netplan/*.yaml</p>
<pre><code>renderer 밑에 아래와 같은 내용을 추가한다. 아래 내용은 IP를 192.168.197.218로 지정한 예
```bash
ethernets:
    enp2s0:
            dhcp6: no
                addresses: [192.168.197.218]
                gateway4: 192.168.197.1
                nameservers:
                    addresses: [8.8.8.8, 8.8.4.4]</code></pre><p><code>:wq</code>로 저장한 뒤에 네트워크 설정을 시스템에 적용한다.</p>
<pre><code class="language-bash">~$ sudo netplan apply</code></pre>
<p>정상적으로 IP가 변경되었는지 확인하려면 다시 <code>ifconfig -a</code>를 통해 확인이 가능하다.</p>
<h3 id="nvidia-driver-설치">Nvidia Driver 설치</h3>
<p>뻘짓을 많이 하는 구간인데, autoinstall을 통해 편하게 할 수 있다고 한다.</p>
<pre><code class="language-bash">~$ sudo add-apt-repository ppa:graphics-drivers/ppa
~$ sudo apt update
~$ sudo ubuntu-drivers autoinstall
~$ sudo reboot</code></pre>
<p>혹시나 설치 과정에서 기존 설치된 프로그램들과 충돌 발생 시 아래 명령어 입력</p>
<pre><code class="language-bash">~$ sudo apt --purge autoremove nvidia*</code></pre>
<p>의존성 에러가 발생하는 경우는 아래 명령어 입력</p>
<pre><code class="language-bash">~$ sudo apt --fix-broken install
~$ sudo apt-get -f install
~$ sudo apt-get upgrade</code></pre>
<h3 id="install-cuda-toolkit">Install CUDA toolkit</h3>
<p>NVIDIA 공식 홈페이지에서 CUDA toolkit을 GPU 버전과 맞는 버전으로 검색하여 설치하면 된다. 하지만 본인이 특수한 버전을 설치해야 하는 상황이라면 아래처럼 하면 큰일난다. <strong>진짜 큰일난다.</strong>
작성일 기준(2020-07-08) <strong>cuda-11.0</strong>이 설치된다.</p>
<pre><code class="language-bash">~$ wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu1804/x86_64/cuda-repo-ubuntu1804_10.1.105-1_amd64.deb
~$ sudo dpkg -i cuda-repo-ubuntu1804_10.1.105-1_amd64.deb
~$ sudo apt-key adv --fetch-keys https://developer.download.nvidia.com/compute/cuda/repos/ubuntu1804/x86_64/7fa2af80.pub
~$ sudo add-apt-repository &quot;deb http://developer.download.nvidia.com/compute/cuda/repos/ubuntu1804/x86_64/ /&quot;
~$ sudo apt-get update
~$ sudo apt-get -y install cuda</code></pre>
<p>혹시나 cuda-11.0이 설치되어 마음이 아프다거나, 많이 속상한 경우에는 아래와 같이 진행하면 된다.</p>
<pre><code class="language-bash">~$ sudo apt-get install cuda-10.1</code></pre>
<p>정상적으로 설치가 되었다면 진심으로 축하드립니다. <code>cd</code> 명령어를 통해 home 디렉토리로 이동하여 <code>.profile</code>을 띄우고</p>
<pre><code class="language-bash">~$ cd
~$ vi .profile</code></pre>
<p>아래 내용을 추가하고 변경사항을 저장한다.</p>
<pre><code class="language-bash">export PATH=/usr/local/cuda-10.1/bin:$PATH
export LD_LIBRARY_PATH=/usr/local/cuda-10.1/lib64:$LD_LIBRARY_PATH</code></pre>
<pre><code class="language-bash">~$ source .profile</code></pre>
<h3 id="install-nvidia-docker">Install NVIDIA Docker</h3>
<p>get.docker.com에 등록되어 있는 스크립트를 바로 로컬에서 실행하는데, 이 스크립트는 기본적으로 docker-ce(Community Edition) repository를 가르키고 있기 때문에 바로 실행시켜주면 됨.</p>
<pre><code class="language-bash">curl -fsSL https://get.docker.com/ | sudo sh</code></pre>
<p>Docker 설치가 완료되면 nvidia-docker 설치</p>
<pre><code class="language-bash"># If you have nvidia-docker 1.0 installed: we need to remove it and all existing GPU containers
docker volume ls -q -f driver=nvidia-docker | xargs -r -I{} -n1 docker ps -q -a -f volume={} | xargs -r docker rm -f
sudo apt-get purge -y nvidia-docker

# Add the package repositories
curl -s -L https://nvidia.github.io/nvidia-docker/gpgkey | \
sudo apt-key add -
distribution=$(. /etc/os-release;echo $ID$VERSION_ID)
curl -s -L https://nvidia.github.io/nvidia-docker/$distribution/nvidia-docker.list | \
sudo tee /etc/apt/sources.list.d/nvidia-docker.list
sudo apt-get update

# Install nvidia-docker2 and reload the Docker daemon configuration
sudo apt-get install -y nvidia-docker2
sudo pkill -SIGHUP dockerd

# Test nvidia-smi with the latest official CUDA image
docker run --runtime=nvidia --rm nvidia/cuda:10.2-base nvidia-smi</code></pre>
<p>위 코드 실행 후 <code>nvidia-smi</code>가 실행된다면 정상적으로 설치가 된 것이다.</p>
<h2 id="ubuntu-settings">Ubuntu Settings</h2>
<p>ubuntu에서 기본적으로 사용할 세팅들을 적어보려 한다.</p>
<h3 id="adduser">adduser</h3>
<pre><code class="language-bash">~$ sudo adduser &lt;ID&gt;</code></pre>
<h3 id="docker-permission">Docker permission</h3>
<pre><code class="language-bash">~$ sudo usermod -aG docker &lt;ID&gt;</code></pre>
<h3 id="directory-permission">Directory permission</h3>
<pre><code class="language-bash">~$ sudo chmod -R a+rws [directory]</code></pre>
<h3 id="ssh-server">SSH Server</h3>
<pre><code class="language-bash">~$ sudo apt-get update
~$ sudo apt-get install openssh-server</code></pre>
<p><code>openssh-server</code> 가 정상적으로 설치되었는지 확인.</p>
<pre><code class="language-bash">~$ dpkg -l | grep openssh</code></pre>
<p>정상적으로 설치되어 있다면 아래 스크립트를 통해 <code>ssh</code>를 실행한다.</p>
<pre><code class="language-bash">~$ sudo service ssh start</code></pre>
<h4 id="to-be-continued">To be continued...</h4>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TIL] Artificial Intelligence[1] : July 7, 2020]]></title>
            <link>https://velog.io/@re_brother/TIL-Artificial-Intelligence1-July-7-2020</link>
            <guid>https://velog.io/@re_brother/TIL-Artificial-Intelligence1-July-7-2020</guid>
            <pubDate>Tue, 07 Jul 2020 01:54:07 GMT</pubDate>
            <description><![CDATA[<h3 id="intro">Intro</h3>
<p>딥러닝/인공지능과 관련된 강의를 듣게 되어 기초부터 처음부터 차근차근 천천히 정리하며 학습을 진행해보려 한다. <del>누구 한 명은 봐주겠지.</del></p>
<h2 id="training-logic">Training Logic</h2>
<p><img src="https://images.velog.io/images/re_brother/post/9a748084-fc81-413d-a8ee-a2654e09220e/image.png" alt="">
학습용 <code>Data</code>가 주어지게 되면 컴퓨터는 이를 기반으로 <code>Model</code>을 생성하게 된다. 이 <code>Model</code>을 기반으로 컴퓨터는 예측을 하고, 컴퓨터가 예측한 데이터와 <code>Data</code>에 Label된 데이터를 기반으로 <code>Loss</code>를 산출하게 되고, 이 <code>Loss</code>를 기반으로 <code>Optimization</code>을 하는 과정을 반복하여 <code>Result</code>를 출력한다.</p>
<h3 id="data">Data</h3>
<ul>
<li><code>Model</code>을 만들기 위한 데이터</li>
<li>모델에 들어가기 전 데이터 전처리 필요</li>
<li><code>Model</code>에 데이터를 넣을 시 Batch로 만들어서 <code>Model</code>에 입력</li>
</ul>
<h3 id="model">Model</h3>
<ul>
<li><code>LeNet</code>, <code>AlexNet</code>, <code>VGG</code>, <code>ResNet</code> 등 성격에 맞게 분류해주는 다양하게 설계된 모델 존재</li>
<li><code>Convolution Layer</code>, <code>Pooling</code> 등 다양한 Layer 구성</li>
<li><code>Model</code>에 Training Param 존재</li>
<li>학습하려는 데이터의 종류에 따라 가장 효율적인 <code>Model</code>을 선택하는 것이 관건</li>
</ul>
<h3 id="prediction--logic">Prediction / Logic</h3>
<ul>
<li>컴퓨터가 <code>Model</code>을 기반으로 각 Class별로 예측한 값
<img src="https://images.velog.io/images/re_brother/post/b441231e-3988-4775-beb1-05150e3253ff/image.png" alt=""></li>
<li>예를 들면, 컴퓨터에 각 과일에 대한 데이터를 기반으로 <code>Model</code>을 생성했다고 하면, 컴퓨터는 어떠한 특정 값이 입력되었을 때 이 <code>Model</code>을 기반으로 해당 입력 값에 대한 예측을 진행한다.</li>
<li>이 때 각 Class별로 어느 정도 값이 일치하는 지에 대해 수치 상으로 출력하게 되며, 이를 <code>Prediction</code>이라고 한다.</li>
<li>위 이미지를 예로 들자면 컴퓨터가 사과일 확률 0.65, 수박일 확률 0.15, 딸기 0.1, 포도 0.05, 바나나 0.05 라고 예측했으며, 이 중 가장 높은 값이 모델이 예상하는 <code>Class</code> 또는 정답이다.</li>
<li>위 값이 정답이라고 할 때 얼마나 맞았는지, 틀렸는지 확인이 가능</li>
</ul>
<h3 id="loss--cost">Loss / Cost</h3>
<ul>
<li>예측한 값이 입력 시 Label된 정답과 비교해서 정답률을 확인</li>
<li><code>Cross Entropy</code>와 같은 다양한 <code>Loss Function</code> 존재</li>
<li><code>Loss</code>는 오답률을 말하며, 이 값을 최소화 시키는 것이 학습의 과정</li>
</ul>
<h3 id="optimization">Optimization</h3>
<ul>
<li>Loss 값을 최소화 하기 위한 과정</li>
<li>내부 Weight 변경을 통해 <code>Model</code> 최적화를 진행하는 과정</li>
</ul>
<h3 id="result">Result</h3>
<ul>
<li>위 과정을 우리가 원하는 만큼 반복한 뒤에 나오는 최종 결과물</li>
<li>평가할 때 또는 예측된 결과를 확인할 때는 예측된 값에서 <code>argmax</code>를 통해 가장 높은 값을 예측한 <code>class</code></li>
</ul>
]]></description>
        </item>
    </channel>
</rss>