<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>yunsung_.log</title>
        <link>https://velog.io/</link>
        <description>웹과 앱을 사랑하는 남자</description>
        <lastBuildDate>Thu, 09 May 2024 05:22:43 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>yunsung_.log</title>
            <url>https://images.velog.io/images/yunsung_/profile/0f6a656f-a5b4-438b-9036-57de4e375465/팽귄.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. yunsung_.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/yunsung_" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[고졸부터 취업 및 2년차 개발자가 이직을 생각하기까지..]]></title>
            <link>https://velog.io/@yunsung_/%EA%B3%A0%EC%A1%B8%EB%B6%80%ED%84%B0-%EC%B7%A8%EC%97%85-%EB%B0%8F-2%EB%85%84%EC%B0%A8-%EA%B0%9C%EB%B0%9C%EC%9E%90%EA%B0%80-%EC%9D%B4%EC%A7%81%EC%9D%84-%EC%83%9D%EA%B0%81%ED%95%98%EA%B8%B0%EA%B9%8C%EC%A7%80</link>
            <guid>https://velog.io/@yunsung_/%EA%B3%A0%EC%A1%B8%EB%B6%80%ED%84%B0-%EC%B7%A8%EC%97%85-%EB%B0%8F-2%EB%85%84%EC%B0%A8-%EA%B0%9C%EB%B0%9C%EC%9E%90%EA%B0%80-%EC%9D%B4%EC%A7%81%EC%9D%84-%EC%83%9D%EA%B0%81%ED%95%98%EA%B8%B0%EA%B9%8C%EC%A7%80</guid>
            <pubDate>Thu, 09 May 2024 05:22:43 GMT</pubDate>
            <description><![CDATA[<h2 id="들어가며">들어가며..</h2>
<blockquote>
<p>이 글은 제가 마이스터고를 거치며 취직후 회사를 다니며 겪었던 경험을 공유합니다.</p>
</blockquote>
<blockquote>
<p>편하게 한 고졸 개발자가 풀어주는 썰이라고 생각하시면 좋을듯합니다. 🥸</p>
</blockquote>
<hr>
<h2 id="고등학생-개발자의-학교개발-생활-🚐">고등학생 개발자의 학교개발 생활 🚐</h2>
<h3 id="나는-결정장애">나는.. 결정장애?</h3>
<p>저의 경우 특목고인 소프트웨어마이스터고를 졸업하였습니다. 저는 학교에서 평범하다면 평범한? 학생이였고 고등학생 당시에도 역시 개발을 좋아하는 한 사람이였습니다.</p>
<p>다만, 여러 고민이 있었는데 내가 하고싶은 개발 분야를 감 잡지 못했다는것이였습니다.</p>
<h3 id="그래도-취직은-해야지">그래도 취직은 해야지..</h3>
<p>다른 친구들은 1학년부터 하고싶은 분야 (back-end , front-end, ai , hw 등등..) 를 미리 정해 다음 학년인 2학년부터 해당 분야를 파는것에 반해 저는 게임개발을 먼저하다 안드로이드 , 웹 프론트엔드를 거칠정도로 잘 한 분야에 정착하지 못하는 성격을 지녔고 이러다보니, 한 기술에 대해 깊게 정착하지 못하여 이대로는 취업에 불리할거같아 안드로이드 분야를 적극적으로 공부하였고 교내 및 교외 행사 , 개인적으로 여러 사이드 프로젝트도 만들었습니다.</p>
<p>이때에 저는 사이드 프로젝트를 만드는것을 좋아했고 또 즐겼습니다. ( 오류는 좋아하지 않았지만..)</p>
<p>사이드 프로젝트를 엄청나게 멋있게 만들지 못했지만 그래도 개발을 좋아하는 마음을 좋게 봐주신 기업이 있어 3학년때 취직을 할 수 있었던것 같습니다.</p>
<p>이에 대한 글은 따로 작성해보겠습니다 :)</p>
<h2 id="나의-처음-직장이자-새로운-사회-📕">나의 처음 직장이자 새로운 사회 📕</h2>
<blockquote>
<p>아직 미처 나에게 주어진 사회가 끝나기도 전에 새로운 사회를 맞이하는 도전</p>
</blockquote>
<h3 id="새로운-시작은-늘-설레지만은-않지">새로운 시작은.. 늘 설레지만은 않지</h3>
<p>이때의 저는 굉장히 마음이 두근거리고 또한 두려웠습니다. 그 이유는 고등학교도 졸업을 안한 내가 회사에 소속되며 회사 업무를 할 수 있었기 때문입니다.</p>
<p>또한, 고등학생인 저로써 무언가 실수하지않을까 혹은 회사의 업무를 해내지 못하면 어쩔지.. 고민을 많이 했었던거 같습니다.</p>
<h3 id="안드로이드-키오스크">안드로이드 키오스크..?</h3>
<p>회사에서 제가 가장 처음으로 받은 업무는 AI 동영상을 이용한 롤베이스 형태의 안드로이드 키오스크 였습니다. </p>
<p>키오스크에 말을 하면 STT 로 말을 인식하고 해당되는 질문에 대한 답을 해주는 간단한 롤베이스 였으나 기존에 제가 제작하던 방식과 많이 달라 파악하는데 시간이 걸렸던 기억이 있습니다.</p>
<p>추가적인 질문지 애니메이션이나 인식 후 자동 스크롤 및 여러 기능 최적화를 해보니 이제 진짜 내가 회사원이구나.. 하는 생각도 들고 상당히 흥분된 상태에서 개발 했었습니다.
또한 , 해당 앱은 키오스크에 들어가는앱이라 용량적인 문제도 있어 추가로 들어가야하는 애니메이션을 android compose 로 직접 구현했던 기억이 있습니다.</p>
<p>그래도 처음 맡았던 프로젝트이니 만큼 애정이 가는 프로젝트였기도 하고 사내에서 해당 키오스크를 이용해 CES 관련 사업도 진행한것을 공유 받아서 뿌듯하기도 했던 프로젝트였습니다 :)</p>
<h3 id="web--mobile-동시-공부">Web , Mobile 동시 공부</h3>
<blockquote>
<p>모바일 개발자에서 웹 개발자로?</p>
</blockquote>
<p>안드로이드 키오스크 프로젝트를 무사히 마친후 저에게 주어진 업무는 식단관리 관련 솔루션 개발이였습니다. 다만, 이 프로젝트에서 APP은 이미 만들어진 보일러플레이트 코드가 존재하였고 WEB 은 만들어지지 않은 상태였습니다.</p>
<p>그래서 APP 에서 사용되어진 라이브러리인 React Native 를 공부하여야하였고 추가적으로 웹도 만들어야 하는 상황이였기 때문에 React 를 먼저 공부하게 되었습니다.</p>
<p>React 는 이미 제가 학교에서도 몇번 경험해보았었고 JS 나 TS 도 경험해 보았어서 2주간의 업무 병행 학습으로 개발 방향성이 잡혔습니다.</p>
<p>다만, 이러한 학습에서 React Native 도 어느정도 진행해볼 수 있었지만 Web 은 프로젝트 특성상 백엔드 , 프론트엔드의 코드를 한번에 관리할 수 있는 SSR 프레임워크인 Remix 를 사용했습니다. 다만, 이때 Remix 를 도입한것은 프레임워크를 비교하며 결정한것이 아닌 최근 트렌딩에 Remix 프레임워크가 자주 보이고 또한 흥미가 생겨 도입하게 되었습니다. </p>
<p>( Next.js 를 도입할껄.. 추후 후회했습니다..)</p>
<p>이때 두가지 플렛폼을 동시에 맡으며 여러가지 라이브러리나 코드 스타일을 내 의지대로 도입도 해보고 시행 착오도 겪으며 많이 성장했던거 같습니다.</p>
<h3 id="1년차-주니어인-내가-여기선-10만-서비스-관리까지">1년차 주니어인 내가 여기선 10만+@ 서비스 관리까지?</h3>
<p>어느날, 저에게 회사에서 겪었던 업무중 가장 고난을 겪고 , 성장아닌 성장을 했던 프로젝트가 할당되었습니다.</p>
<p>해당 프로젝트는 회사에서 외주로 진행하는 외부 프로그램 유지보수였는데 해당 프로그램은 웹 / 관리자 웹 / 사용자 멤버십 웹으로 이루어져있었습니다.</p>
<p>다만, 프로그램에서 제가 경험해보지못한 JSP 및 각종 Spring 프레임워크를 사용하였었습니다. 저로써는 프로젝트를 받고 당황했습니다.</p>
<p>당장 지금부터 해당 프로젝트에 사용된 프레임 워크를 모두 공부하고 (특정 회사의 private 프레임 워크도 포함되어 있어 쉽지않았습니다..) 명목상 유지보수지만 신규 개발도 앞으로 한달 안에 해야하는 상황이였고 추가로 이미 10만+@ 사용자가 사용하고 있는 서비스라 부담도 많았습니다.</p>
<p>프로젝트를 하며 걱정했던대로 제가 모르는 부분에서 오류가 터지는것이 주를 이뤘고 프로젝트에서 사용하던 JAVA 버전이나 여러 프레임워크들이 노후화되어 유지보수하는데에도 난항을 겪었습니다.</p>
<p>그럼에도 불구하고 저와 같이 프로젝트를 진행하던 Back-end 개발자분과 함께 멤버십 회원 로그인 방식 변경 및 여러 이슈 트레킹 + 신규 기능 개발등 여러 시행착오를 겪으며 100+@ 의 pr 이 생겼을정도로 성장도 많이 되었지만 번아웃도 함께왔던 프로젝트였습니다.</p>
<h3 id="내가-원하는-성장-≠-회사가-바라는-업무-처리">내가 원하는 성장 ≠ 회사가 바라는 업무 처리</h3>
<p>제가 원래 원하는 성장 및 개발 방향은 팀원간의 소통이 주력이 되며 가독성이 좋은코드가 무엇인지 연구하며 클린코드 방향성이 지켜지며 개발하는것이였습니다.</p>
<p>다만, 현재 회사에서는 한가지를 깊게 유지보수하는것이 아닌 여러 프로젝트를 동시다발적으로 개발하여 업무 던지기 식으로 진행되었습니다. 이러한 환경에서 저는 업무를 하던 어느순간 제가 원하는 가치의 성장을 할 수 없다는것을 깨달았습니다. </p>
<p>( 나는 코드를 보고 수정 및 추가만 하는 사람인가..?)</p>
<p>또한, 개발 환경상으로 사수분이나 다른 코드를 봐주실 수 있는 개발자분이 존재하지 않다는 점 및 한번 프로젝트가 종료된 코드에 대해서 코드 클린을 안한다는점 + 저의 코드를 리뷰 받을 창구가 어디에도 없다는점<strong>(개인적인 공부에서도 주변에 도움을 받을 수 있는 환경이 없었다.)</strong> 에서 저의 성장은 매말라 있었다고 생각합니다.</p>
<p>그래서 이때부터 개인적으로 클린코드를 공부하여 새로운 프로젝트들은 처음 만들때 , 만들어 진후에 클린코드를 회사 코드에 적용시켰던거같습니다.</p>
<h3 id="클린코드-공부">클린코드 공부</h3>
<p>회사일을 하던 어느 순간.. 저는 문득 이런생각이 들었습니다.</p>
<blockquote>
<p><strong>내가 지금 일하며 쓰는 코드가 내가 추구하는 코드가 맞나?</strong></p>
</blockquote>
<p>회의감이 점점 더 커져가며 생각해보기론 바쁜 프로덕트에 맞추려는 제 자신만 보였고 제가 추구했던 &quot;개발자면 누구든지 볼 수 있는 코드&quot; 와는 거리가 멀었습니다. </p>
<p>실제로 제가 만들어놓은 컴포넌트들을 봐도 가독성 제로에 가까운 컴포넌트와 props drilling 현상으로 인해 의존성이 서로 연결되어버린 코드들.. 밖에 보이지 않았거든요.</p>
<p>그래서 차근차근 시작했습니다.</p>
<p>SOLID 기법을 이용한 react 에서 추구하는 이상적인 컴포넌트 생성 및 분리 방식 및 여러 디자인 패턴, 추가적인 web 내부 동작과정 / 테스트 코드 , 스타일 선언 방식 등등 </p>
<p>이전에 급하게 공부하느라 하지못했던 공부를 진행하였고 또한, 공부를 하며 이전에 풀리지 않았던 최적화 문제나 궁금했던 사항들도 여럿 알 수 있었습니다.</p>
<p>다만 , 제가 궁극적으로 추구했던 <strong>누구나 알 수 있는 코드</strong> 를 충족하기에는 다른 사람의 의견이 수용되지않은 제 주관적인 코드는 아직 부족했습니다. </p>
<p>🖥❤️ (코드로 토론을 해보고 싶은 내 마음) </p>
<p>개인적인 공부에서도 성장할 수 있는 한계를 느끼게 되었습니다..</p>
<hr>
<h2 id="결론">결론</h2>
<p>저는 위의 배경을 바탕으로 저는 코드리뷰가 존재하고 회사의 플랫폼 방향성 및 유저에게 직접 피드백을 받으며 일 할 수있는 환경에 대해 목말라지기 시작했습니다.</p>
<p>이 다음 글에서는 위의 배경을 바탕으로 시작했던 이직기에 대해 이야기해보겠습니다.</p>
<p>긴글 읽어주셔서 감사합니다!</p>
<p><strong>내가 원하는 성장을 얻기위해.. 또한, 내가 추구하는 가치를 위해..</strong></p>
<p><img src="https://velog.velcdn.com/images/yunsung_/post/3a89e4ad-77ae-45e7-8fc8-176f40f94680/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Bundler 간단하게 이해하기]]></title>
            <link>https://velog.io/@yunsung_/Bundler-%EA%B0%84%EB%8B%A8%ED%95%98%EA%B2%8C-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@yunsung_/Bundler-%EA%B0%84%EB%8B%A8%ED%95%98%EA%B2%8C-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0</guid>
            <pubDate>Fri, 12 Apr 2024 06:00:54 GMT</pubDate>
            <description><![CDATA[<h2 id="번들러는-왜-세상에-나오게-되었을까">번들러는 왜 세상에 나오게 되었을까?</h2>
<p><strong>“초기의 웹사이트는 지금과 같이 크지 않았습니다.”</strong></p>
<p><img src="https://velog.velcdn.com/images/yunsung_/post/a813dacb-3bd4-4e14-a47a-d252fcf3e96a/image.png" alt=""></p>
<p>다만 지금은 컴퓨터의 사양도 더 좋아지고 네트워크 속도도 더 빨라지며 비약적인 기술적 성장이 이루워졌습니다.</p>
<p>위 배경에서 웹 어플리케이션의 규모는 더욱 커져갔습니다.</p>
<p>당연하겠지만 프로그램이 커짐에 따라 <strong>파일의 양과 크기가 다양</strong> 해지고 많아졌습니다.</p>
<p>이러한점은 기존에 사용하던 웹에 다양한 문제를 야기시켰습니다.</p>
<h3 id="1-1-중복된-이름으로-된-에러">1-1. 중복된 이름으로 된 에러</h3>
<p>대규모 웹페이지의 경우 <strong>수많은 JS 파일</strong>이 있고, <strong>여러사람이 협업하여 관리</strong>하다보니 함수나 변수명이 중복될 가능성이 있습니다.</p>
<p><strong>index.html</strong></p>
<pre><code class="language-html">&lt;head&gt;
    &lt;script src=&quot;./source/WordCode.js&quot;&gt;&lt;/script&gt;
    &lt;script src=&quot;./source/WordCode2.js&quot;&gt;&lt;/script&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;h1&gt;Hello, Webpack&lt;/h1&gt;
    &lt;div id=&quot;root&quot;&gt;&lt;/div&gt;
    &lt;script type=&quot;module&quot;&gt;
        document.querySelector(&quot;#root&quot;).innerHTML = duplication;
    &lt;/script&gt;
&lt;/body&gt;</code></pre>
<p><strong>WordCode.js</strong></p>
<pre><code class="language-jsx">var duplication = &quot;Hello&quot;;</code></pre>
<p><strong>WordCode2.js</strong></p>
<pre><code class="language-jsx">var duplication = &quot;World&quot;;</code></pre>
<p>위 예제의 결과는 당연하게도 “word” 라는 변수가 중복되었기때문에 마지막에 import 되었던 </p>
<p>world.js 에 있는 word 변수의값인 <strong>“World”</strong> 가 렌더링 될것입니다.</p>
<p>이처럼 중복이 되는 함수나 변수명이 있다면 의도치않은 <strong>Side Effect</strong> 가 일어날 수 있습니다.</p>
<h3 id="1-2-파일로-인한-문제">1-2. 파일로 인한 문제</h3>
<p><img src="https://velog.velcdn.com/images/yunsung_/post/cbd9a1dd-2ca1-4d91-931e-b8b5e127d6b2/image.png" alt=""></p>
<p>현재 프론트엔드를 공부하신분이면 아시겠지만 사용자가 웹 페이지를 들어가게 되면</p>
<p>DNS 서버에서 일련의 과정을 통해 해당 웹페이지가 올라가있는 IP 에서 웹 페이지 파일을 요청하게됩니다.</p>
<p>이과정에서 웹 어플리케이션을 구성하는 파일의 양이 많다면 사용자의 요청에 대해 응답하는 시간이 길어지게됩니다. 이때, 사용자 트레픽이 많아지면 응답을 제때 하지 못해 네트워크 병목현상이 일어날 수 있습니다.</p>
<p>해당 문제를 해결하기위해 하나의 JS 파일에 모든 로직이나 페이지를 몰아 작성한다면 위 문제를 어느정도 해결할 수있지만 개발자는 하나의 파일에서 수 천, 수 만줄의 코드를 유지보수 해야 하기 때문에 가독성 면에서 정말 정말 지옥이 펼쳐질것입니다.</p>
<p>즉 , <strong>파일이나 함수를 분리</strong>하면 개발자는 편하지만 <strong>실제 사용자에게 영향</strong>이가고</p>
<p>분리하지않고 <strong>한번에 관리</strong>하게되면 <strong>사용자에게 가는 영향은 줄어들겠지만 개발자 고통의 초행길</strong>일것입니다.</p>
<h2 id="번들러">번들러</h2>
<p><strong>번들러(Bundler)</strong> 는 <strong>이러한 과정</strong>을 <strong>효과적으로 해결</strong>하기위해 등장하였습니다.</p>
<p>번들러가 위 문제를 해결하기 위해 제시한 방향은 다음과 같습니다.</p>
<ul>
<li>여러개의 파일을 하나로 묶어주기</li>
</ul>
<p>음.. 여러개의 파일을 하나로 묶어준다?? 아직은 감이 재대로 오지 않습니다. </p>
<p>이해를 위해 여러 번들러중 가장 사용량이 높은 WebPack 을 중심으로 살펴보겠습니다.</p>
<p><img src="https://velog.velcdn.com/images/yunsung_/post/d6a9ece5-bf60-4dd0-bec4-5487508f75dd/image.png" alt=""></p>
<p>WebPack 은 애플리케이션에 필요한 모든 <strong>모듈</strong>을 알아서 매핑하며, 매핑 된 결과를 가지고 하나 이상의 번들(파일 묶음)을 생성합니다. </p>
<p>이를 이해하기 위해선 모듈의 개념을 알아야 합니다.</p>
<h2 id="모듈">모듈</h2>
<p>모듈의 사전적 정의는 다음과 같습니다.</p>
<blockquote>
<p>프로그램 내부를 기능별 단위로 분할한 부분</p>
</blockquote>
<p>우리가 사용하는 WebPack 이 동작하는 개념도 똑같습니다.</p>
<p>Javascript에서 말하는 모듈도 마찬가지입니다. 웹 애플리케이션을 구성하는 기능들을 분할한 단위라고 생각하면 됩니다. </p>
<p>즉, 코드를 기능에 따라 여러가지 파일로 <strong>분리 및 결합</strong>하는 것입니다.</p>
<h3 id="번들러의-동작-한눈에-보기">번들러의 동작 한눈에 보기</h3>
<p><strong>hello.js</strong></p>
<pre><code>var word = &quot;Hello&quot;;

export default word;</code></pre><p><strong>world.js</strong></p>
<pre><code>var word = &quot;World&quot;;

export default word;</code></pre><p><strong>index.html</strong></p>
<pre><code>&lt;body&gt;
    &lt;h1&gt;Hello, Webpack&lt;/h1&gt;
    &lt;div id=&quot;root&quot;&gt;&lt;/div&gt;
    &lt;script type=&quot;module&quot;&gt;
        import hello_word from &#39;./source/hello.js&#39;;
        import world_word from &#39;./source/world.js&#39;;
        document.querySelector(&quot;#root&quot;).innerHTML = hello_word + &quot; &quot; + world_word;

    &lt;/script&gt;
&lt;/body&gt;</code></pre><p>위의 코드를 보면 WebPack 이 적용된 코드는 <strong>“export default”</strong> 로 export 될 부분을 미리 export 하고 </p>
<p>실제로 사용할 부분에서 import 로 받아오고있습니다.</p>
<p>이렇게 모듈을 사용한다면 하기와 같은 장점을 얻을 수 있습니다.</p>
<ul>
<li>자주 사용되는 코드를 별도의 파일로 만들어서 <strong>필요할 때마다 재활용</strong>할 수 있다.</li>
<li>코드를 개선하면 이를 사용하고 있는 <strong>모든 애플리케이션의 퍼포먼스가 개선</strong>된다.</li>
<li>코드 수정 시에 필요한 로직을 빠르게 찾을 수 있다. ( <strong>가독성이 강화</strong>된다. )</li>
<li><strong>필요한 로직만을 로드</strong>해서 메모리의 낭비를 줄일 수 있다.</li>
<li>한번 다운로드된 모듈은 웹브라우저에 의해서 저장되기 때문에 <strong>동일한 로직을 로드 할 때 시간과 네트워크 트래픽을 절약</strong> 할 수 있다. ( 브라우저에서만 해당 )</li>
</ul>
<h3 id="번들러의-동작--기능을-분할하는-번들러-">번들러의 동작 ( 기능을 분할하는 번들러 )</h3>
<p><img src="https://velog.velcdn.com/images/yunsung_/post/928e11c6-9cf9-42a4-bef9-ca75b26788d1/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/yunsung_/post/9bc8281d-a5cd-474c-85a0-5cd2b6f4e03c/image.png" alt=""></p>
<p>위의 그림을 보면 기존에 사용하던 2개의 js 를 index 에서 사용하는 modle 로 묶어 하나의 js 파일로 통합하였습니다. 이때, 종속성을 알아서 확인하여 사용하지 않은 파일은 포함하지 않습니다.</p>
<p>이에따라 우리는 다음과 같은 이점을 얻을 수 있습니다.</p>
<ul>
<li><p><strong>파일의 크기를 줄여 페이지 로딩을 빠르게 하는 효과</strong>를 발휘합니다. 
→ 이를 통해 <strong>네트워크 병목현상을 최소화</strong> 할 수 있습니다.</p>
</li>
<li><p>함수나 변수 이름의 중복이 있더라도 각각 다르게 <strong>import 를 해주기때문에 SideEffect 를 방지</strong>합니다.
→ 모듈단위 코딩의 이점인 <strong>코드의 가독성이나 유지보수를 신경</strong>쓰며 코딩이 가능합니다.</p>
</li>
</ul>
<hr>
<h2 id="그외의-특징">그외의 특징</h2>
<h3 id="es6-버전-이상의-스크립트를-사용-가능">ES6 버전 이상의 스크립트를 사용 가능</h3>
<p>webpack은 로더(babel-loader)를 통해 Babel이라는 ES6 이상의 자바스크립트 문법을 ES5 버전의 자바스크립트 문법으로 변환시켜주는 트랜스파일러를 사용할 수 있습니다. 이를 통해 ES5만 지원하는 오래된 브라우저에서도 ES6 이상의 문법으로 이루어진 JS 파일을 작동할 수 있게 해줍니다.</p>
<h3 id="sass를-사용-가능">SASS를 사용 가능</h3>
<p>webpack은 style-loader와 css-loader, sass-loader라는 로더를 사용하여 SASS를 CSS로 컴파일하여 사용할 수 있게 만들어줍니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[테스트코드 입문 주니어의 개념 공부하기]]></title>
            <link>https://velog.io/@yunsung_/%ED%85%8C%EC%8A%A4%ED%8A%B8%EC%BD%94%EB%93%9C-%EC%9E%85%EB%AC%B8-%EC%A3%BC%EB%8B%88%EC%96%B4%EC%9D%98-%EA%B0%9C%EB%85%90-%EA%B3%B5%EB%B6%80%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@yunsung_/%ED%85%8C%EC%8A%A4%ED%8A%B8%EC%BD%94%EB%93%9C-%EC%9E%85%EB%AC%B8-%EC%A3%BC%EB%8B%88%EC%96%B4%EC%9D%98-%EA%B0%9C%EB%85%90-%EA%B3%B5%EB%B6%80%ED%95%98%EA%B8%B0</guid>
            <pubDate>Thu, 04 Apr 2024 07:30:30 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/yunsung_/post/0fa78870-33e3-42ce-8a73-e93f974a9a2d/image.png" alt=""></p>
<blockquote>
<p><del>테</del>스트 테<del>스트 테테테텥테텥 테스트</del></p>
</blockquote>
<h2 id="들어가며">들어가며..</h2>
<p>요즘 채용공고를 보면 항상 우대사항 혹은 필수역량에 언급되는 기술스택이 있습니다.
바로 <del><strong>시니어같은 주니어</strong></del> 가 아니라 <strong>테스트 코드 작성</strong> 경험 혹은 여부이죠.</p>
<p>사실, 테스트 코드는 채용공고말고도 우리가 심심치않게 항상 말하는 주제이긴 합니다.
우리에게 익숙한 React 라이브러리에서도 컴포넌트 분리의 중요성 측면에서 &quot;테스트 코드의 작성이 쉬워진다&quot; 라는 장점이 있고요.</p>
<p>우리는 이렇듯 평소 테스트코드의 존재는 알았지만 테스트코드에 입문하기 껄끄러웠을수도 있습니다. 그 이유는</p>
<blockquote>
<ul>
<li>실제 구현도 시간이 부족한데 테스트 코드까지 작성하려면 시간이 너무 오래걸려! 😈</li>
<li>어플리케이션 변경 사항을 테스트코드에 까지 업데이트해야해서 테스트코드도 유지보수해야해.. 😵💫</li>
</ul>
</blockquote>
<p>하지만, 테스트코드에는 단점만 있는것이 아닌 장점도 있습니다. <del>나름 무서운 친구는 아니랍니다</del>
그럼 이번글에서 테스트코드와 같이 친해지는 시간을 가져보아요~</p>
<h2 id="테스트코드-공부-계기">테스트코드 공부 계기</h2>
<p>본인은 사실 테스트 코드말고도 아직 모르는게 많은 주니어 개발자입니다. 따라서 일단 여러가지 기술스택을 공부하며 프로젝트 설계 및 기능구현도 벅찬 상황이였고 이 상황에서 </p>
<blockquote>
<p><strong>TDD 관점</strong> 으로 코딩을 하는것은 매우 어렵다. </p>
</blockquote>
<p>라는 이야기와 또한 회사에 아직 <strong>테스트코드</strong> 기술이 도입되지 않았다는 이유로 테스트 코드 설계에 대한 내용을 배우지 않는것을 <strong>정당화</strong> 하였습니다.</p>
<p>하지만, 시간이 지나며 나름 회사에서 진행한 프로젝트도 몇 있고 해당 프로젝트를 진행하며 또 진행 후에 유지보수 및 인수인계를 하며 내가 짠 코드가 <strong>어떤 아키텍처로 설계되어 있으며</strong> 추가로 <strong>컴포넌트 하나하나마다 어떤 생각으로 코드를 설계</strong>하였는지 기억이 나지않아 하나하나 다시 코드를 봐야하는 상황이 생겼습니다.</p>
<p><img src="https://velog.velcdn.com/images/yunsung_/post/6634bbe8-1aeb-4478-9bcd-0ad729b8f208/image.png" alt=""></p>
<p><del>전 개발자가 어떤사람이였길래 ㄷㄷ... 난 누군지 정말 모르겠다</del></p>
<p>그래서 테스트코드를 사용해 TDD 기법을 이용하면 나의 주니어 시기에 성장에 큰 도움이 될거같아 공부하게 되었습니다.</p>
<h2 id="테스트의-정의-및-테스트코드-사용-이유">테스트의 정의 및 테스트코드 사용 이유</h2>
<blockquote>
<p>설이나 이론이 실제로 들어맞는지를 확인하기 위해 다양한 조건 아래에서 여러 가지 측정을 실시하는 일 ( 위키백과 )</p>
</blockquote>
<p>우리 위키백과 선생님 말씀으론 위와 같이 말하십니다.</p>
<p>사실 우리가 평소에 하는 테스트(테스트 코드를 사용하지않고)도 위 사전적 정의를 그대로 따라갑니다.</p>
<blockquote>
<ul>
<li>설이나 이론 : <strong>디자이너가 준 피그마 / 기획자의 기능정의서</strong></li>
<li>다양한 조건 아래서 여러가지를 측정 확인 : <strong>개발자가 개발하면서 진행하는 디버깅 및 QA</strong></li>
</ul>
</blockquote>
<p>즉, 우린 이미 &quot;테스트&quot; 라는 정의에 부합하는 일을 이미 하고있다는겁니다.</p>
<p><del>나 사실 일 잘하는 개발자일지도??</del></p>
<p>하지만, 이렇게 우리가 평소에 하는 테스트도 한계점이 있는데</p>
<p>만약에 기능이 매우 많거나 코드를 수정했는데 해당 기능과 관련된 다른 다양한 기능들이 있다고 가정해봅시다. (예를들면 회원가입 기능?)</p>
<p>이 상황에서 리팩토링 조금 한걸로 이 모든것에 대한 테스트 (비지니스 로직의 플로우 모두)를 계속 하게 된다면 매우 비효율적일테고 그렇다고 리팩토링을 안하자니 기술 부채는 증가할 것이고 악순환이 될겁니다.</p>
<p><img src="https://velog.velcdn.com/images/yunsung_/post/50ddd903-bca6-417b-9221-64ce0a523054/image.png" alt=""></p>
<p><del>ㅋㅋㅋㅋㅋㅋ 기술부채 막이래</del></p>
<p>이 상황에서 구세주 같은 코드가 바로 <strong>&quot;대 황 테스트 코드&quot;</strong> 되시겠습니다.</p>
<p><strong>대 황 코드</strong>를 작성하게 된다면</p>
<blockquote>
<ul>
<li>적극적인 리팩토링이 가능해지며 결함을 예방하고 요구사항을 충족하는지 검사할 수 있다. </li>
<li>테스트 코드가 명세서의 역할을 하여 코드를 파악하는데 도움을 주기 때문에 협업에서도 유용하다. </li>
<li>더 나아가 테스트 자동화로 반복적인 테스트에 대한 비용이 줄어든다.
(테스트 코드를 작성하는 비용이 있긴하지만..)</li>
</ul>
</blockquote>
<p>이러한 부분들에서 이점을 얻을 수 있습니다.</p>
<p>다만, 모든것이 완벽한 팔방미인도 내성발톱은 있는법..</p>
<blockquote>
<ul>
<li>테스트 코드를 작성하는 비용이 있습니다. 이때문에...
(모든 부분에서 하는것은 비효율적이기 때문에 프로젝트를 설계해야하는데 테스트 코드만 짤 수는 없습니다.)</li>
</ul>
</blockquote>
<p>따라서, 테스트 코드를 사용하기 위해선 테스트 코드가 테스트하는 기능의 중요도를 잘 체크해야합니다.</p>
<h2 id="프론트엔드-테스트-종류">프론트엔드 테스트 종류</h2>
<p>프론트엔드 테스트의 종류는 크게 4가지로 나눌 수 있습니다.</p>
<blockquote>
<ul>
<li>정적테스트</li>
<li>단위테스트</li>
<li>통합테스트</li>
<li>E2E (End to End)</li>
</ul>
</blockquote>
<h3 id="단위테스트">단위테스트</h3>
<blockquote>
<p>나는 나만 신경쓰고 나만의 길로만 간다 기호 2번 단위테스트~</p>
</blockquote>
<p>단위테스트는 말 그대로 어플리케이션을 가장 작은 단위로 분리하여 테스트하는것을 의미합니다.</p>
<p>프로젝트를 설계하면서 <strong>util 함수, 커스텀 훅, 컴포넌트 파일</strong> 등을 생성하게 된다. 이에 대한 <strong>가장 작은 단위</strong>의 테스트 코드를 설계하는것이 단위 테스트이다.</p>
<p>컴포넌트를 테스트할 경우 자식 컴포넌트까지 포함하여 렌더링하는 <strong>Sociable Test</strong>가 있고, 자식 컴포넌트를 Mocking해서 렌더링하는 <strong>Solitary Test</strong>가 있습니다.</p>
<p>여기서 <strong>Mocking</strong> 이란 모조품을 만들어 일단 코드가 돌아가게 하는것입니다. 컴포넌트의 자식 컴포넌트가 있을경우 자식 컴포넌트의 모조품을 만들어 가짜로 대체하는 것입니다.</p>
<p>테스트하고 싶은 기능이 다른 기능들과 엮여있을 경우 유용하게 사용할 수 있습니다.
(jest 의 jest.fn() 함수느낌..)</p>
<p><img src="https://velog.velcdn.com/images/yunsung_/post/431560d9-7993-4106-b216-710eb9d99bfc/image.png" alt=""></p>
<p>단위 테스트를 함으로써 얻는 이점은 아래와 같습니다.</p>
<blockquote>
<ul>
<li>수행 목적에 따른 행위가 뚜렷한 함수와 컴포넌트등을 설계하기위해 고려하게된다.
이벤트도 발생시킬 수 있어 기존에 하던 콘솔 디버깅도 할 수 있다.</li>
</ul>
</blockquote>
<h3 id="통합intergration-테스트">통합(Intergration) 테스트</h3>
<blockquote>
<p>나는 모든걸 한번에 하기를 좋아해.. 한번 엮이면 일단 같이 움직여야하는 기호 3번 통합테스트</p>
</blockquote>
<p>어플리케이션의 여러 부분들이 통합되어 제대로 상호작용 되는지 테스트합니다. 단위테스트는 단일 모듈에서만 테스트를 진행하여 자신의 단위 내에서만 에러를검증할 수 있었다면, 통합테스트는 모듈 간의 연결에서 발생하는 에러를 검증할수 있고, 단위 테스트보다 넓은 범위에서 테스트하므로 리팩토링할 경우 사이드 이펙트가 적습니다.</p>
<p>의존성 있는 모든 모듈이 연결된 상태로 테스트하는 <strong>board test</strong>와 연결된 모듈을 모킹 또는 가상의 API로 대체하여 테스트하는 <strong>narrow test</strong>가 있습니다.</p>
<p>프론트엔드에서의 통합 테스트는 <strong>state 변화에 따른 UI변경, UI와 API간의 상호작용</strong>에 대한것이 있습니다.</p>
<p><strong>단위테스트와의 차이점</strong> 을 살펴보면 아래와 같습니다.</p>
<blockquote>
<ul>
<li>단위 테스트와 달리 개발자가 변경할 수 없는 부분까지 묶어 검증할 때 사용합니다. (Ex. 외부 라이브러리)</li>
<li>단위 테스트에서 발견하기 어려운 버그를 찾을 수 있습니다.</li>
<li>단위 테스트보다 더 많은 코드를 테스트하기 대문에 신뢰성이 떨어질 수 있고 어디에서 문제가 발생했는지 확인하기 어렵습니다.</li>
</ul>
</blockquote>
<p><img src="https://media2.giphy.com/media/i5RWkVZzVScmY/giphy.gif?cid=790b76115ce903e8786752696b73dcb3&rid=giphy.gif" alt=""></p>
<p><del>이게바로 무한동력 자동문??</del></p>
<h3 id="e2e-end-to-end">E2E (End to End)</h3>
<blockquote>
<p>나는 모두를 수용할 수 있는 능력을 지닌 기호 4번 E2E~</p>
</blockquote>
<p>유닛 테스트로 함수를 테스트하고, 통합 테스트로 서로 다른 시스템이 잘 상호작용하는지 테스트합니다. E2E 테스트는 종단(Endpoint)간 테스트로 유저의 입장에서 유저가 애플리케이션을 사용하는 상황을 가정하고 테스트를 수행합니다.</p>
<p><img src="https://velog.velcdn.com/images/yunsung_/post/c417d845-fb0b-4da7-8ec7-11becc0a88c8/image.png" alt="">
<del>개발자는 결코 유저를 만만하게 봐서는 안돼..(출처 : 청원이 유튜브)</del></p>
<h2 id="각-목적에-맞는-개발-라이브러리">각 목적에 맞는 개발 라이브러리</h2>
<h3 id="단위테스트-1">단위테스트</h3>
<h4 id="jest">Jest</h4>
<p>jest는 단위 테스트를 위한 프레임워크입니다. JavaScript로 작성된 모든 코드의 테스트를 지원하고 있으며 독립적으로 실행이 가능합니다.</p>
<p>TypeScript에서도 @types/jest와 jest에서 typescript를 실행할 수 있게 도와주는 ts-jest만 있으면 사용할 수 있습니다.</p>
<hr>
<h3 id="통합테스트">통합테스트</h3>
<h3 id="enzyme">enzyme</h3>
<p>Jest가 자바스크립트 테스트 프레임워크라면, enzyme은 React를 테스트하기 위해 airbnb에서 만든 라이브러리이다. enzyme는 독립적으로 사용할 수 없기 때문에 Jest와 함께 사용하여 React의 단위 테스트와 통합 테스트가 가능해진다.</p>
<h3 id="react-testing-libraryrtl">react-testing-library(RTL)</h3>
<p>enzyme와 다르게 컴포넌트 구현 세부 사항에 중점을 두지 않고, 실제 브라우저 DOM을 기준으로 테스트를 작성합니다. 그래서 RTL은 컴포넌트의 속성(state, props 등)에 대한 접근을 제공하지 않습니다.</p>
<hr>
<h3 id="e2eend-to-end">E2E(End to End)</h3>
<h3 id="cypress">cypress</h3>
<p>E2E 테스트(단위 테스트와 통합 테스트도 가능)가 가능한 도구이다. GUI 도구를 지원하고, 스펙관리 및 디버깅이 편리합니다. test runner가 jest가 아닌 mocha라는 특징이 있습니다.</p>
<hr>
<h3 id="마치며">마치며</h3>
<p>그동안 미뤄왔던 테스트 코드의 개념을 한번 되돌아보며 정리하는 시간을 가졌습니다.
다가가기 어려웠던 친구이니만큼 잘 활용해서 멋진 프론트엔드 개발자가 되어보겠습니다!</p>
<p>이제 테스트코드 넌 내 단짝이야~</p>
<p><img src="https://velog.velcdn.com/images/yunsung_/post/a34d38d3-4353-4ffc-815d-e77f02435369/image.png" alt=""></p>
<p>추가로 다음은 <strong>TodoList + Jest + ReactTestingLibrary</strong> 를 이용한 실제 코드를 가져오겠습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React Testing Setting ( Jest )]]></title>
            <link>https://velog.io/@yunsung_/React-Testing-Setting-Jest</link>
            <guid>https://velog.io/@yunsung_/React-Testing-Setting-Jest</guid>
            <pubDate>Thu, 28 Mar 2024 09:04:58 GMT</pubDate>
            <description><![CDATA[<h3 id="환경-세팅"><strong>환경 세팅</strong></h3>
<br>

<p><strong><em>CRA 명령어</em></strong></p>
<pre><code class="language-bash">npx create-react-app react-test --template typescript </code></pre>
<p><strong><em>Jest 설치 및 여러 부수 라이브러리 설치</em></strong></p>
<pre><code class="language-bash">npm i jest jest-dom jest-environment-jsdom ts-jest babel-jest --dev</code></pre>
<p><strong><em>testing-library 를 위한 모듈들 설치</em></strong></p>
<pre><code class="language-bash">npm i @testing-library/user-event @testing-library/react @testing-library/jest-dom @testing-library/dom --dev</code></pre>
<p><strong><em>package.json scripts 영역에 명령어 추가</em></strong></p>
<pre><code class="language-json">&quot;test&quot;: &quot;jest --watch --passWithNoTests&quot;,
&quot;test:ci&quot;: &quot;jest --ci --passWithNoTests&quot;</code></pre>
<p><strong><em>tsconfig / basic setting</em></strong></p>
<pre><code class="language-js">{
  &quot;compilerOptions&quot;: {
    &quot;baseUrl&quot;: &quot;./src&quot;,
    &quot;paths&quot;: {
      &quot;@/*&quot;: [&quot;./*&quot;],
      &quot;@pages/*&quot;: [&quot;pages/*&quot;],
      &quot;@components/*&quot;: [&quot;components/*&quot;],
      &quot;@utils/*&quot;: [&quot;utils/*&quot;],
      &quot;@hooks/*&quot;: [&quot;hooks/*&quot;],
      &quot;@types/*&quot;: [&quot;types/*&quot;]
    },
      //...
  },
  &quot;include&quot;: [&quot;next-env.d.ts&quot;, &quot;**/*.ts&quot;, &quot;**/*.tsx&quot;],
  &quot;exclude&quot;: [&quot;node_modules&quot;]
}</code></pre>
<p><strong><em>tsconfig-setup</em></strong></p>
<pre><code class="language-js">{
  &quot;compilerOptions&quot;: {
    &quot;target&quot;: &quot;es5&quot;,
    &quot;baseUrl&quot;: &quot;src&quot;,
    &quot;paths&quot;: {
      &quot;@/*&quot;: [&quot;./*&quot;],
      &quot;@pages/*&quot;: [&quot;pages/*&quot;],
      &quot;@components/*&quot;: [&quot;components/*&quot;],
      &quot;@utils/*&quot;: [&quot;utils/*&quot;],
      &quot;@hooks/*&quot;: [&quot;hooks/*&quot;],
      &quot;@types/*&quot;: [&quot;types/*&quot;]
    },
    &quot;lib&quot;: [
      &quot;dom&quot;,
      &quot;dom.iterable&quot;,
      &quot;esnext&quot;
    ],
    &quot;allowJs&quot;: true,
    &quot;skipLibCheck&quot;: true,
    &quot;esModuleInterop&quot;: true,
    &quot;allowSyntheticDefaultImports&quot;: true,
    &quot;strict&quot;: true,
    &quot;forceConsistentCasingInFileNames&quot;: true,
    &quot;noFallthroughCasesInSwitch&quot;: true,
    &quot;module&quot;: &quot;esnext&quot;,
    &quot;moduleResolution&quot;: &quot;node&quot;,
    &quot;resolveJsonModule&quot;: true,
    &quot;isolatedModules&quot;: true,
    &quot;noEmit&quot;: true,
    &quot;jsx&quot;: &quot;react-jsx&quot;
  },
  &quot;include&quot;: [&quot;next-env.d.ts&quot;, &quot;**/*.ts&quot;, &quot;**/*.tsx&quot;, &quot;jest.config.ts&quot;,&quot;src/jest-setup.ts&quot;],
  &quot;exclude&quot;: [&quot;node_modules&quot;]
}
</code></pre>
<p><strong>package.json 명령어 추가</strong></p>
<pre><code class="language-json"> &quot;scripts&quot;: {
    &quot;start&quot;: &quot;react-scripts start&quot;,
    &quot;build&quot;: &quot;react-scripts build&quot;,
    &quot;test&quot;: &quot;jest --watch --passWithNoTests&quot;,
    &quot;test:ci&quot;: &quot;jest --ci --passWithNoTests&quot;,
    &quot;eject&quot;: &quot;react-scripts eject&quot;
  },</code></pre>
<hr>
<h3 id="환경-세팅중-이슈-해결"><strong>환경 세팅중 이슈 해결</strong></h3>
<blockquote>
<p><strong>(SyntaxError: Support for the experimental syntax ‘jsx’ isn’t currently enabled)</strong></p>
</blockquote>
<blockquote>
<p><strong>ReferenceError: React is not defined</strong></p>
</blockquote>
<p>.babelrc 및 웹팩 설정시 JSX 변환 오류가 있었다.</p>
<p>해당 error 는 웹팩에서 실행하는 babel-loader 에서 JSX의 &lt;&gt; 태그 문법이 변환이 되지않아 에러가 나는 상황이였다.</p>
<p>해당 에러는 다음과 같이 해결할 수 있었다.</p>
<p><strong>1).babelrc 생성</strong>  ( 해결 )</p>
<pre><code class="language-js">{
    &quot;presets&quot;: [
      &quot;@babel/preset-env&quot;,
      [&quot;@babel/preset-react&quot;, { &quot;runtime&quot;: &quot;automatic&quot; }],
      &quot;@babel/preset-typescript&quot;
    ]
}</code></pre>
<p>*<em>2)babel.config.js 생성 *</em></p>
<pre><code class="language-js">module.exports = {
  presets: [
    [&quot;@babel/preset-react&quot;, { runtime: &quot;automatic&quot; }],
};</code></pre>
<p>또한, 위와 같이 설정을 해주니 <strong><em>ReferenceError: React is not defined</em></strong> 이슈도 함께 해결되었다.</p>
<blockquote>
<p><strong>typeError: expect(...).toHaveAttribute is not a function</strong></p>
</blockquote>
<p>[Git hub 이슈] (<a href="https://github.com/testing-library/react-testing-library/issues/379)%EB%A5%BC">https://github.com/testing-library/react-testing-library/issues/379)를</a> 살펴보니 toHaveAttribute 같은 속성은 기본적으로 
<em><strong>import &quot;@testing-library/jest-dom/extend-expect&quot;;</strong></em> 를 추가해주어야한다.</p>
<p>test file 에 하기 import 를 추가했다.</p>
<pre><code class="language-js">import &quot;@testing-library/jest-dom/extend-expect&quot;;</code></pre>
<hr>
<p>역시 항상 초기세팅이 어려운것 같다..</p>
<p>다음에는 테스트 코드의 개념과 TDD 개발론을 정리해 오겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[StoryBook 넌 누구냐]]></title>
            <link>https://velog.io/@yunsung_/StoryBook-%EB%84%8C-%EB%88%84%EA%B5%AC%EB%83%90</link>
            <guid>https://velog.io/@yunsung_/StoryBook-%EB%84%8C-%EB%88%84%EA%B5%AC%EB%83%90</guid>
            <pubDate>Wed, 19 Jul 2023 07:44:57 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>이 글은 StoryBook 에 대해 <strong>가볍게</strong> 정리한 글입니다.</p>
</blockquote>
<h1 id="배경">배경</h1>
<p>회사에서 컴포넌트를 여러 사람과 협업해 개발하게 되면 나 외의 다른 사람이 만든 컴포넌트의 의도나 쓰는 방법을 잘 알지 못해 생각보다 협업에 차질을 겪는 일이 발생했었습니다. </p>
<p>해당 이유로 코드에 주석이나 미리 인자를 정해놓고 개발을 진행 했어도 , 시간이 많이 소요된다는 단점이 있어 해결방법을 찾아야 했습니다. 그러다 문득 다른 기업은 어떤 방식으로 협업하는지 방법을 찾던중 <a href="https://www.codenary.co.kr">해당 사이트</a>에서 여러 기업이 자주 중복적으로 사용하는 <strong>Story Book</strong> 을 발견하게 되었습니다.</p>
<h1 id="storybook-에-대해">StoryBook 에 대해</h1>
<blockquote>
<p>Storybook is a frontend workshop for building UI components and pages in isolation. </p>
</blockquote>
<p>StoryBook 을 한마디로 요약하면 위와 같습니다.
그말인 즉슨 UI 개발 환경이며 , 동시에 UI 컴포넌트 PlayGround 라고 할 수있습니다.
심지어 UI 를 테스팅 함과 동시에 컴포넌트 문서화 까지 가능합니다.</p>
<p>이는 React 의 특징중 하나인 <strong>잘 분리시킨 컴포넌트를 이용해 재 사용성을 높이는 것</strong> 임에 미루어 봤을 때, Storybook은 단위 테스트 측면이나 컴포넌트 분리 측면에서 React와 꽤나 잘 어울리는 UI 테스팅 및 문서화툴 이라고 생각합니다.</p>
<h1 id="storybook-의-장점">StoryBook 의 장점</h1>
<p><span style="color: #d4feff"><em><strong>독립적으로 UI를 구축 할 수있습니다.</strong></em></span></p>
<p>Story Book 을 사용하면 UI의 모든 부분이 구성 요소가 됩니다. 구성 요소의 장점은 단순히 렌더링 방식을 보기 위해 전체 앱을 분리 시킬 필요가 없다는 것입니다. 요소의 데이터를 변경하거나 이벤트를 조작하여 특정한 UI를 렌더링할 수 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/yunsung_/post/66c4e354-437b-494b-81d4-fbb8ceeb046b/image.png" alt=""></p>
<p><span style="color: #d4feff"><em><strong>특정한 스냅샷을 스토리로 만들고 테스트할 수 있습니다.</strong></em></span></p>
<p>재사용을 위해 만들어진 컴포넌트들을 Story에서 조합하여 기존 코드를 건드릴 필요없이 실시간으로 테스트하며 개발 할 수있습니다. 기존처럼 테스트를 위해 특정 페이지에 만들고 지울 필요 없이, 새로운 Story 객체 하나만 만들어 테스트할 수 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/yunsung_/post/107dbc5a-f83b-4b66-9b98-6ff41bf7f8c3/image.png" alt=""></p>
<p><span style="color: #d4feff"><em><strong>협업을 위한 문서화를 할 수있습니다.</strong></em></span></p>
<p>다음과 같이 상태 자체를 Story 에 저장해 해당 컴포넌트의 인자 설명이나 특정 컴포넌트의 상태가 어떤 형태로 사용될 수 있는지 직관적으로 문서화 할 수있습니다.</p>
<p><img src="https://velog.velcdn.com/images/yunsung_/post/6d11563c-8ea4-4aa5-b54d-9c6402afafdd/image.png" alt=""></p>
<h1 id="storybook-설치-및-세팅">StoryBook 설치 및 세팅</h1>
<blockquote>
<p>제가 사용한 버전은 7.1 버전입니다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/yunsung_/post/6fe90450-4df2-4ed4-87f2-47df383c65e6/image.png" alt=""></p>
<p>필자는 프로젝트 root 에 storyBook 을 설치했다. 설치하면 두개의 디렉토리가 생기게 되는데,</p>
<p><strong><em>.storybook : Storybook 설정 파일 포함 / main.js &amp; preview.js</em></strong></p>
<p><strong>_src/stories: Storybook 예제 컴포넌트들 / exComponent _</strong></p>
<p>해당 파일은 각각 storyBook 의 설정과 예시 컴포넌트들이 들어있는 파일이 들어있다.</p>
<h2 id="mainjs">main.js</h2>
<p><img src="https://velog.velcdn.com/images/yunsung_/post/026bcc04-3280-47ea-84d3-a6f8dc38cbd7/image.png" alt=""></p>
<p>main.js 에는 storybook을 위한 config 설정들이 담겨있습니다. <em><strong>@storybook/cli sb init</strong></em> 을 통해서 기본으로 설정되는 stories와 addons 세팅을 하였습니다. (stories에서 story 파일이 프로젝트 내 어디에 어떤 형식의 파일이 위치하는지를 명시해 주어야 storybook 실행 시 정상적으로 불러올 수 있습니다.)</p>
<h2 id="previewjs">preview.js</h2>
<p><img src="https://velog.velcdn.com/images/yunsung_/post/48a60677-2935-4aaf-ae39-b7a4edf277a0/image.png" alt=""></p>
<p>preview.js 에는 해당 프로젝트의 모든 Story에 global하게 적용될 포맷을 세팅하는 곳 입니다. 후에 나오겠지만, parameter와 decorater는 Story의 프로퍼티, 여기선 포맷에 해당합니다.</p>
<blockquote>
<p>⚠️ 주의
현재 프로젝트에선 <strong>tailwindcss</strong> 를 이용해 세팅되어 있으므로 ThemeProvider를 통해 감싸주고 GlobalStyle도 적용시켜 주었습니다. 해당 사례처럼 따로 외부 라이브러리를 적용시켜주지않으면 <strong>StoryBox</strong> 에도 적용되지 않습니다.</p>
</blockquote>
<p>parameters 에 적용된 속성은 <em><strong>@storybook/cli sb init</strong></em> 을 통해 기본적으로 세팅된 값 입니다. </p>
<h1 id="storybox-사용하기">StoryBox 사용하기</h1>
<p><img src="https://velog.velcdn.com/images/yunsung_/post/af18bdd6-b3c3-4481-8f5b-ac170f54f04c/image.png" alt=""></p>
<p>우선, 해당 명령어로 StoryBox 서버를 가동해야합니다. 서버는 특별한일이 있지않은 이상은 포트번호 6006으로 열리지만 해당 포트를 이미 사용중이라면 자동으로 다른 포트로 변환해 열립니다.</p>
<p>열어보면 예시 컴포넌트들을 확인해 볼 수있는데, 해당 컴포넌트들은 <em><strong>root/stories</strong></em> 하위에 있습니다.
해당 폴더 하위에 새 컴포넌트를 작성해도 되고 외부에 컴포넌트를 구현해 story 폴더에 끌고오거나 아에 외부에서 story 컴포넌트를 만들 수도있습니다.</p>
<h2 id="새로운-story-생성">새로운 Story 생성</h2>
<p><img src="https://velog.velcdn.com/images/yunsung_/post/f0f3c714-c072-4e0f-bfb5-2468c100e736/image.png" alt=""></p>
<p>새로운 Story 를 생성하기 위해선 이름에 stories 가 들어가야 하기때문에
<strong><em>{ component name } . stories . ts</em></strong> 가 되어야합니다.</p>
<p>새로운 파일을 생성하고 meta 와 story / story componet 를 작성해주도록 합시다.</p>
<h3 id="meta-에-대해">Meta 에 대해</h3>
<p>meta 의 경우 <span style='color:#fff5b1'><em><strong>title , component , tags , argTypes</strong></em></span> 로 나눌 수 있는데, 각 요소를 설명하자면</p>
<p><strong>Title</strong></p>
<p>스토리북 폴더 계층 구조 , Example/Button 을 예시로 사용하면 StoryBook 페이지에서 Example 폴더 안에 Button 파일이 생성되게 됩니다.</p>
<p><strong>Component</strong></p>
<p>옵셔널이지만 addon 을 위해 써주는것이 권장되는 Story 에 명시적으로 어떤 컴포넌트를 문서화 할지 적는 인자입니다. 해당 인자에는 새로운 Story로 만들 import 해온 컴포넌트를 넣습니다.</p>
<p><em><strong>tags</strong></em></p>
<p>스토리에 대한 자동 문서화를 활성화하거나 비활성화합니다. 곧 나올 컴포넌트 문서화를 위한 인자입니다.</p>
<p><em>*<em>argTypes *</em></em></p>
<p>story 에 대한 유동적인 메타데이터를 정의하는 인자입니다. 사용자가 storyBook 에서 컨트롤 할 수있는 인자를 정의합니다.</p>
<h3 id="args">Args</h3>
<p>모든 meta 인자를 정의해 주었다면 해당 Meta 인자를 Story 객체에 타입으로 넣어 args 에 담긴 인자들에 대한 컴포넌트를 캡처해주어야 합니다.</p>
<p>Story는 렌더링된 UI 컴포넌트의 state를 캡쳐하여 어떻게 컴포넌트가 렌더링 되는지 보여줄 수있습니다. 이때, Story는 React의 props와 비슷한 느낌으로 arguments를 args 라는 이름으로 가지고 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/yunsung_/post/6db9de50-b7ab-4b9a-9253-74817f9e790b/image.png" alt=""></p>
<p>위와같이 args 를 이용해 정해진 인자값을 다르게 바꾸게 되면 story 객체는 여러개가 되고 StoryBook 에서 볼 때 여러 상태의 컴포넌트를 확인해 볼 수있습니다.</p>
<p>새로 생성한 파일에 제가 설명한 모든 인자가 있다면 포트를 열었던 사이트에서는 밑과 같이 여러 args 로 캡쳐된 컴포넌트를 볼 수 있습니다.
<img src="https://velog.velcdn.com/images/yunsung_/post/19bb67b1-604a-46c5-8bad-44117c0a4778/image.png" alt=""></p>
<p>또한, 사용자가 StoryBook 에 들어가 실제 컴포넌트들의 args 을 controls 탭에서 args의 값을 자유롭게 수정할 수 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/yunsung_/post/8758dbac-9ab5-4573-9a58-f1a78459b8c6/image.png" alt=""></p>
<h2 id="component-문서화">component 문서화</h2>
<p>컴포넌트를 문서화 할 수있습니다.</p>
<p><img src="https://velog.velcdn.com/images/yunsung_/post/3ee330fd-51e5-4a0b-9a56-c06e0001aeac/image.png" alt=""></p>
<p>다음과 같이 컴포넌트 인자값에 설명을 적게 되면 아까 열었던 storyBook Web 에서 밑과 같이 Description 에 설명이 자동으로 문서화됨을 볼 수있습니다. </p>
<p><img src="https://velog.velcdn.com/images/yunsung_/post/6f5525fe-1525-47d9-a14e-7568ed9953e0/image.png" alt=""></p>
<p>또한, Control 에서 직접 인자값을 바꿔보며 해당 인자값이 어떤 역할을 하는지 확인해볼 수도있습니다.</p>
<h1 id="마치며">마치며</h1>
<p>협업 할 경우 컴포넌트 관련 의사소통에 조금 더 신경을 써야함을 느끼고 StoryBook 을 사용해 보았는데 , 생각보다 UI 단위 테스트와 컴포넌트 문서화에 이점이 강해서 앞으로 회사에서 협업을 할경우 사수님에게 요청하여 자주 사용해보아야겠다고 생각했습니다. 다만 , 해당 사용법은 storyBook 기능의 기능중 기본이기 때문에 , 실질적 실무에서는 웹사이트를 더 참고 해야할것 같습니다.</p>
<p>긴글 읽어주셔서 감사합니다 :)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[주니어의 Remix 도입기]]></title>
            <link>https://velog.io/@yunsung_/%EC%A3%BC%EB%8B%88%EC%96%B4%EC%9D%98-Remix-%EB%8F%84%EC%9E%85%EA%B8%B0</link>
            <guid>https://velog.io/@yunsung_/%EC%A3%BC%EB%8B%88%EC%96%B4%EC%9D%98-Remix-%EB%8F%84%EC%9E%85%EA%B8%B0</guid>
            <pubDate>Tue, 18 Jul 2023 05:41:17 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>해당글은 회사 프로젝트를 진행하며 도입한 Remix 라는 라이브러리 도입기와 
Remix 라이브러리에 대해 다룹니다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/yunsung_/post/109dcede-a9b1-46c9-a3ec-0eeb8b221644/image.png" alt=""></p>
<p>어느날 회사에서 프로젝트 관련 요청 토큰이 할당되었습니다.</p>
<p>해당 요청은 새롭게 고객사 웹을 제작하라는 요청이였는데 전에 만들어 배포했던 식단관리 관련 앱의 유저 데이터를 한눈에 볼 수 있도록 관리자 페이지를 제작해야한다는 요청이였습니다.</p>
<p>평소 개발은 React 로 했으나 새롭게 만들어야할 고객사 웹은 관리자 페이지로 다른 웹들과 달리 데이터를 DB에서 가져올 경우가 많았는데, 해당 상황에서 CSR 를 사용한다면 서버 요청시간이 길어질수도 있던 상황이기도 했고 백엔드 엔지니어분들의 일정 부족으로 백엔드 코드까지 일부 건드려야할 상황이 생겼습니다.</p>
<p>해당 상황에서 저는 UI 와 API 를 분기하기보단 한 프로젝트에서 관리하는것이 편할것 같다고 생각했고 , API 요청과 UI 렌더링을 동시에 할 수 없는 CSR 보단 SSR 즉 , 서버 사이드 렌더링이 해당 프로젝트에 더욱 적합하다고 생각했습니다.</p>
<h2 id="ssr-csr">SSR? CSR?</h2>
<p>우선 왜 Remix 를 도입했는지 알기 위해선 위에서 잠깐 언급했던 두가지 렌더링 방법을 살펴보아야합니다.</p>
<p>간단하게 말하자면 CSR 은 <strong>웹페이지 렌더링이 클라우드(브라우저) 측에서 일어나는것을 의미</strong>합니다.</p>
<p><img src="https://velog.velcdn.com/images/yunsung_/post/1e812816-9a4d-415b-964e-46b118a76d8a/image.png" alt=""></p>
<p>클라이언트 사이드 렌더링은 UI 렌더링을 브라우저에서 모두 처리하는 것입니다. 즉, 자바스크립트를 불러온다음에 실행이 되어야 우리가 만든 화면이 사용자에게 보여집니다.</p>
<p>CRA로 만든 프로젝트의 개발 서버를 실행한 다음, 크롬 개발자 도구의 Network탭을 열고 <a href="http://localhost:3000/">http://localhost:3000/</a> 페이지의 요청에 대한 응답을 보시면 다음과 같이 root 엘리먼트가 비어 있는 것을 확인할 수 있습니다.</p>
<p>즉, 해당 페이지는 처음에는 빈 페이지입니다. 해당 페이지는 JS 가 실행되어야 비로소 UI 가 로드됩니다.</p>
<p>하지만 SSR 은 <strong>SSR은 서버에서 첫 페이지의 렌더링을 클라이언트 측이 아닌 서버 측에서 처리해주는 방식</strong>입니다.</p>
<p>서버에서 페이지의 렌더링을 클라우드가 아닌 서버측에서 관리해주기때문에 JS 가 실행되지않아도 UI 가 이미 로드되어있습니다.</p>
<p>해당 상황은 웹 검색 기능을 구현하기에도 유리한데, 서버 사이드 렌더링을 하여 사용자에게 페이지를 더욱 일찍 보여준다면, 페이지의 모든 데이터가 로딩될 때 까지 기다리지 않고 검색 할 가능성이 있기 때문에 의도한 데이터가 제대로 검색되지 않을 수 도 있습니다. </p>
<p>제가 구현해야하는 웹은 검색기능 및 렌더링 측면과 함께 렌더링 시점에서 다량의 DB 데이터를 가져와야하기때문에 SSR 방식이 적합했습니다.</p>
<h2 id="remix">Remix</h2>
<blockquote>
<p>그래서 Remix 가 뭔데?</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/yunsung_/post/ed8b7b5f-abc7-45f7-9773-4f2f1b237c3e/image.jpeg" alt=""></p>
<p><a href="https://remix.run/">Remix 공식 문서</a></p>
<p>Remix는 React를 사용하는 풀스택 웹 프레임워크입니다. Prisma 및 디렉토리 기반 라우팅 구성으로   기존에 구현하기 까다로웠던 SSR 을 Remix 라이브러리로 간편하게 구현 할 수있습니다. </p>
<h2 id="remix-의-신세계">Remix 의 신세계</h2>
<p>Remix 를 디렉토리 기반 라우팅 , loader 및 action , from 개념 등 실질적 웹 구현에 있어서 
처음보는 Remix 만의 특징이 정말 신기했습니다.</p>
<ol>
<li><p>디렉토리 기반 라우팅</p>
<p>기존에 우리가 리액트 라우터를 사용할 때 라우트를 구성할 때에는, <strong>컴포넌트 선언 방식</strong>으로 설정을 했었던 반면, Remix의 경우에는 <strong>컴포넌트의 디렉터리에 기반하여 라우트</strong> 가 설정이 됩니다.</p>
<p><em>app/routes/index.jsx</em> 와 같이 말이죠.</p>
<p>따라서 밑의 사진과 같이 프로젝트 파일이 구성되고 해당 파일 주소에 따라 라우팅이 되는 방식이 신기했습니다.</p>
</li>
</ol>
<p><img src="https://velog.velcdn.com/images/yunsung_/post/07f99cc1-22a7-483d-a9d6-65d916a810f5/image.png" alt=""></p>
<ol start="2">
<li><p>loader 및 action</p>
<p> Remix 에서는 최초 로딩시 <strong>loader</strong> 라는 함수가 호출됩니다. 해당 함수는 최초에 로딩되어야하는 UI     에 API 를 호출 할 때 유리한데 저같은경우는 식단 정보 및 사용자 정보 , 관리자 정보 등등 관리자     페이지에 있어서 미리 호출되어야할 함수나 API 를 다음과 같이 불러 올 수 있었습니다.</p>
<p> 같은 양상으로 action 의 경우는 함수 이름대로 web 에서 어떤 동작이 주어지게 될 경우 실행되는 함수로 버튼이 눌리거나 input box 의 value 가 바뀌게 될 경우 호출되는 함수로, </p>
<pre><code> ![](https://velog.velcdn.com/images/yunsung_/post/dcfa97ae-23f7-40a2-a678-9f877ed173cb/image.png)</code></pre><p> <img src="https://velog.velcdn.com/images/yunsung_/post/d055d7cd-ef72-4353-89ee-4ec0949567e2/image.png" alt=""></p>
<p> 또한,추가적으로 Loader 및 Action 함수를 실제 컴포넌트 단위에서 사용하기위해선 밑과같은 useLoaderData 나 useActionData 를 사용해 UserData.(변수명) 해당 값을 가져올 수 있었습니다.</p>
<pre><code> ![](https://velog.velcdn.com/images/yunsung_/post/70363d91-e990-4964-ac07-08eb9a0b3cee/image.png)</code></pre></li>
<li><p>Form
 가장 핵심적인 부분입니다. 앞서 말했던 Action 에서 button 이나 다른 컴포넌트의 인자를 받고싶다면 form 으로 최상위 부모의 컴포넌트를 묶고 , 보이지 않는 Input 에 data 를 넣어 action 함수에 전달 해 줄 수 있습니다.</p>
<p><a href="https://remix.run/docs/en/1.18.1/guides/data-writes#plain-html-forms">Form 관련 문서</a></p>
<p><img src="https://velog.velcdn.com/images/yunsung_/post/f205c914-06d8-41bc-8e4b-6ce3d88a17a9/image.png" alt="">
 <img src="https://velog.velcdn.com/images/yunsung_/post/98d48437-a8a4-4810-af6b-62e9061b5d8c/image.png" alt=""></p>
</li>
</ol>
<h2 id="도입중-문제점의-시작">도입중 문제점의 시작..</h2>
<p><em><strong>TypeError: Cannot read properties of undefined (reading ‘root’)</strong></em></p>
<p>하지만 모두 잘 되진 않았습니다. <em><strong>TypeError: Cannot read properties of undefined (reading ‘root’)</strong></em> 열심히 로직을 작성하던중 해당 오류가 나오며 정상적으로 실행이 되지 않았습니다. 그래서 Remix 깃헙 이슈를 찾아보니, 저와 같은 문제로 고생중인 분들이 있었는데 원인은 브라우저 번들에 서버 환경에서만 실행 가능한 코드가 포함되었기 때문입니다.</p>
<p>해당 상황에서 해결법을 조금 더 찾아보니 서버 환경에서 실행되어야 하는 코드의 파일 이름을 something.server.ts 처럼 변경하는 방법이 가장 효과적임을 알 수있었고 이렇게 하면 해당 파일이 서버 환경에서만 필요하다는 것에 대한 힌트를 제공하는 방식이 된다는걸 알 수 있었습니다.</p>
<p><strong>해결 방법</strong></p>
<blockquote>
<p>서버 관련한 파일은 server 를 붙여주거나 앞에 api 주소를 붙여 서버에서 사용되는 코드임을 명시하였습니다.</p>
</blockquote>
<p><em><strong>CSS 관련 이슈</strong></em></p>
<p>웹 스타일 관련은 <a href="https://mui.com/">mui</a> 와 <a href="https://tailwindcss.com/">tailwindcss</a> 를 사용하였는데 , mui 커스텀을 할 일이 생겨 기존 react 에서 하던대로 css 파일을 임포트하면 적용이 되지않는 이슈가 발생했었습니다.</p>
<p><strong>해결 방법</strong></p>
<blockquote>
<p>이부분은 공식 문서에도 잘 나와있는데, remix 에서의 css 파일은 root 파일에 반드시 import 를 해주어야합니다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/yunsung_/post/acdb7997-19a5-47df-a072-594ebb3f4d9b/image.png" alt=""></p>
<p><em><strong>root 디렉토리 외의 api 호출</strong></em></p>
<p>유저 파일정보나 세부정보같은 메인에서 호출할경우 로직이 꼬이거나 , 불필요하게 메인에서 호출하지 않아도 되는 api 의 경우 root 디렉토리 외의 컴포넌트에서 호출해야 합니다. 하지만 remix 특성상 외부에서 prisma api 를 호출 하려하면, 다음과 같은 오류가 뜨며 호출이 되지않고 웹에 error 가 발생합니다.</p>
<blockquote>
<p>prisma 함수를 component 에서 직접 호출 할 수없습니다.</p>
</blockquote>
<p>당연히 해당 오류는 db를 직접 참조하는 prisma 함수에서 의도적으로 막아놓은것으로 만약 클라이언트에서 해당 함수를 실행하게된다면 웹이 정상적으로 작동하지않을 수 있습니다.</p>
<p><strong>해결 방법</strong></p>
<p>다음과 같이 fetch 함수를 통해 api 라는 서버에서 쓰는 페이지임을 명시하는 페이지 주소를 만들고 해당 페이지에 호출 할 수 있도록 만들었습니다. 그러나 이 방법이 최선인지는 아직 모릅니다.</p>
<p><img src="https://velog.velcdn.com/images/yunsung_/post/8c1f8e37-d0ad-4b18-bddb-07823e533b1e/image.png" alt="">
<img src="https://velog.velcdn.com/images/yunsung_/post/df77ad33-dc3b-4441-a64f-c93b6fd2b42c/image.png" alt=""></p>
<h2 id="회고--정리">회고 &amp; 정리</h2>
<p>7월 초반쯔음 프로젝트가 마무리 되었고, 앞으로도 유지보수가 걸려있어 유지보수를 하겠지만</p>
<p>어드민은 여러모로 동기부여를 쓸 수 있도록 도와준 프로젝트 였습니다. 특히 파일 구조로 web 주소를 설정하는 부분이나 loader / action 같은 remix 만의 고유 funcion 을 이용하여 신기하고 즐겁게 코드를 쓰는 기분이 너무 좋았습니다. 역시 개발자는 새로운 기술을 배우고 실전에 응용할 때 더욱 기쁜것 같습니다.</p>
<p>혹시 본인처럼 어드민을 만들 계획이 있거나 필요한 사람이 있다면 꼭 Remix로 하지 않아도 된다고 말하고 싶습니다. 내 생각에 어드민에서 중요한 것은 프레임워크보다 코드의 반복을 줄이고, 컴포넌트를 선언적으로 구성할 수 있게 만드는 것이 더 중요하다고 봅니다. <del>사실 난 그냥… Remix를 써보고 싶었을 뿐</del></p>
<p>하지만 아쉬운 부분도 여럿 있었는데, 거의 대부분의 api 가 새로운 api root 를 통해 호출 되었다는점과 여러 커스텀 컴포넌트의 인자 type 구성이 유지보수 대상이나 아직까지 건드리지 않았던 점이 조금 아쉬웠습니다.</p>
<p>물론 아쉬운점은 유지보수측면에서 점차 수정해나갈 예정입니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[구글 플레이 정책의 무서움을 깨달아버렸다..]]></title>
            <link>https://velog.io/@yunsung_/%EA%B5%AC%EA%B8%80-%ED%94%8C%EB%A0%88%EC%9D%B4-%EC%A0%95%EC%B1%85%EC%9D%98-%EB%AC%B4%EC%84%9C%EC%9B%80%EC%9D%84-%EA%B9%A8%EB%8B%AC%EC%95%84%EB%B2%84%EB%A0%B8%EB%8B%A4</link>
            <guid>https://velog.io/@yunsung_/%EA%B5%AC%EA%B8%80-%ED%94%8C%EB%A0%88%EC%9D%B4-%EC%A0%95%EC%B1%85%EC%9D%98-%EB%AC%B4%EC%84%9C%EC%9B%80%EC%9D%84-%EA%B9%A8%EB%8B%AC%EC%95%84%EB%B2%84%EB%A0%B8%EB%8B%A4</guid>
            <pubDate>Fri, 14 Jul 2023 07:04:17 GMT</pubDate>
            <description><![CDATA[<h1 id="app-reject--playstore-이슈--개인정보">App Reject / PlayStore 이슈 / 개인정보</h1>
<p>평화로운 어느날 회사에서 관리하던 앱이 갑자기 소리소문없이 내려갔습니다..!</p>
<p><img src="https://velog.velcdn.com/images/yunsung_/post/033f6172-0699-4077-ab12-ac3b347c6320/image.png" alt=""></p>
<p>어제까지 잘 업데이트 되었던 앱이 하루아침에 사라지게 된계기는 다음과 같습니다.</p>
<blockquote>
<p>&quot;너! 앱에서 개인정보를 수집하는데 왜 정확히 설명안해!&quot;</p>
</blockquote>
<p>우선 저는 개인정보 처리방침 이야기가 있어 개인정보 처리방침부터 점검했습니다.</p>
<p><img src="https://velog.velcdn.com/images/yunsung_/post/286345df-939b-4486-8cec-7f001d0851e1/image.png" alt=""></p>
<p>google 정책을 켜 개인정보 처리방침을 유심히보니 <strong>&quot;앱에서 엑세스 ,수집 , 사용 공유하는 개인 정보 및 민감한 사용자 정보 데이터를 공유하는 모든 주체 공개&quot;</strong> 라는 내용을 발견했습니다.</p>
<p>해당 글을 근거로 저는 개인정보 처리방침을 밑과 같이 교체하였습니다.</p>
<h3 id="개인정보-처리방침-개선-사항">개인정보 처리방침 개선 사항</h3>
<p>주체 공개 및 사용목적을 추가하여 더욱 투명한 개인정보 처리방침을 만들었습니다.</p>
<p><strong><em>추가된 개인정보 처리방침 약관</em></strong></p>
<blockquote>
<p>⛔ ex)
<strong>앱에서 액세스, 수집, 사용, 공유하는 &quot;개인 정보&quot; 및 &quot;민감한 사용자 데이터&quot; 유형</strong></p>
</blockquote>
<ul>
<li>본 앱에서 사용되는 개인정보는 구글 계정과 상관없는 기기의 ID, 비밀번호, 생년월일, 성별, 주소, 핸드폰번호, 이메일주소, 상품수령방법, 상품수령주소,핸드폰 ID, 위치정보, 앱 형태(유/무료), 사용언어, 국가명, 안드로이드 버전이 있습니다.</li>
<li>본 앱에서 사용하는 &quot;민감한 사용자 데이터&quot;는 사용자가 앱을 이용하여 생성하는 &quot;앱 자체 정보&quot;가 있습니다.</li>
<li>본 앱은 위 기술한 &quot;개인 정보&quot;, &quot;민감한 사용자 데이터&quot; 제외하고 이외 데이터를 수집, 사용, 공유하지 않습니다.</li>
<li>이 앱을 사용함으로써 자신의 실제 위치를 본사와 공유하는 데 동의하는 것으로 간주됩니다. 본사는 귀하의 위치 정보를 활용하여 귀하의 모바일 기기가 가진 GPS 기능을 활용하여 (회사명) 의 이벤트를 설계하지만, 이 정보는 귀하가 앱을 통해 데이터를 공개적으로 공유하지 않는 한 본사에서만 접속이 가능합니다.<br></li>
<li><em>개인 정보 및 민감한 사용자 데이터를 안전하게 처리하는 절차 및 사용 목적*</em></li>
<li>&quot;개인 정보&quot;의 ID, 비밀번호, 생년월일, 성별, 주소, 핸드폰번호, 이메일주소, 상품수령방법, 상품수령주소,핸드폰 ID, 위치정보 는 불법적인 중복 사용자를 걸러내는데 사용하거나 고객의 문의사항에 대응하기 위한 자료로 활용됩니다.</li>
<li>앱 형태(유/무료), 사용언어, 국가명, 안드로이드 버전은 앱 개선을 위한 정보로 본 회사 내부에서만 이를 관리합니다.</li>
<li>로그인 시 로그인 정보는 암호화 되어 본 회사의 서버와 통신합니다.</li>
<li>&quot;민감한 사용자 데이터&quot;는 앱을 사용하기 위한 기본적인 앱 데이터 값이며 사용자가 언제든 삭제할 수 있습니다 </li>
</ul>
<h3 id="app-에서-접근하기-쉬운-개인정보-처리방침-버튼-추가">APP 에서 접근하기 쉬운 개인정보 처리방침 버튼 추가</h3>
<p><strong><em>App 개선 사항</em></strong></p>
<blockquote>
<p>⛔ “APK에 중요한 공개가 있지만 공개가 적절하지 않습니다 
앱이 적절한 공개 없이 사용자의 설치된 응용 프로그램 정보를 업로드하고 있습니다.”</p>
</blockquote>
<p>해당 에러에서 지적한 점을 근거로 APP 의 첫 화면에 개인정보 처리방침의 접근이 쉬운 버튼을 배치하였습니다.</p>
<p><img src="https://velog.velcdn.com/images/yunsung_/post/070e5aa0-e562-4432-b8bd-c5252e5c0680/image.png" alt=""></p>
<h3 id="추가적인-조치사항">추가적인 조치사항</h3>
<p>그 후에 추가적으로, <a href="https://play.google.com/console/u/0/developers/5580638715903018995/app/4974426047291815398/app-content/overview">google console</a> 의 앱콘텐츠중 개인정보 처리방침과 데이터 보안 탭을 살펴 보았습니다.</p>
<p>구글 플레이스토어 약관을 살펴보면 2022년 3월경부터 <strong>데이터 보안 설문지</strong>를 작성하지않거나 <strong>개인정보처리방침 url 에 누락된 정보나 url 자체에 문제</strong>가 발생할경우 앱이 PlayStore 에서 내려가도록 되어있습니다.</p>
<p>만약 앱이 이슈로 내려갔다면 기존에 작성되어있던 데이터보안 설문지를 확인하여 앱이 새로운 개인정보를 데이터 보안 설문지에 적혀있는 데이터보다 <strong>추가적인 데이터</strong>를 가져오게 되는지 혹은 <strong>개인정보 처리방침 Url 에 누락되어있는 정보나 url 자체가 접속이 되지 않는지 확인</strong>하고 조치해야합니다.</p>
<p><img src="https://velog.velcdn.com/images/yunsung_/post/397acd54-43e8-4fd3-90f8-bf7390965d02/image.png" alt=""></p>
<h3 id="결과">결과</h3>
<p>성공!!</p>
<p><img src="https://velog.velcdn.com/images/yunsung_/post/9ee599c3-b1b8-4317-84d4-14cbe06cc316/image.png" alt=""></p>
<p>구글 플레이 정책의 무서움을 깨달아버렸다..</p>
<p><img src="https://velog.velcdn.com/images/yunsung_/post/c6355da7-454c-4001-a353-da05dba6c91c/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Reactive Programing 이란?]]></title>
            <link>https://velog.io/@yunsung_/Reactive-Programing-%EC%9D%B4%EB%9E%80</link>
            <guid>https://velog.io/@yunsung_/Reactive-Programing-%EC%9D%B4%EB%9E%80</guid>
            <pubDate>Mon, 28 Mar 2022 07:14:35 GMT</pubDate>
            <description><![CDATA[<h2 id="컴퓨터-프로그램의-종류">컴퓨터 프로그램의 종류</h2>
<ul>
<li><p>주어진 입력값을 바탕으로 결과를 계산하는 변환 프로그램</p>
</li>
<li><p>상호작용 프로그램으로 프로그램이 작업을 주도하며 사용자 혹은 다른 프로그램과 상호작용</p>
</li>
<li><p>리액티브 프로그램은 프로그램 자신의 주변환경과 끊임없이 상호작용을 하는데 프로그램이 
 주도하는것이 아닌 환경이 변하면 이벤트를 받아 동작</p>
</li>
</ul>
<p>데이터 흐름과 전달에 관한 프로그래밍 패러다임.</p>
<h2 id="왜-사용할까">왜 사용할까?</h2>
<p>결론적으로는 UX 경험을 향상시키기 위하여!</p>
<p>앱에서 메인스레드가 멈추거나 느려지지 않도록 해야한다.</p>
<p>하지만 메인스레드를 자유롭게 핸들링 하면서 유지하려면 무겁고 시간이 오래걸리는</p>
<p>작업은 백그라운드에서 해야한다. 백그라운드에서도 무거우면 서버에서.</p>
<p>네트워크 운영을 위한 비동기작업!</p>
<h2 id="비동기-작업은-asynctask로도-가능">비동기 작업은 AsyncTask로도 가능?</h2>
<p>가능. 하지만 2019년 11월 8일 공식적으로 AsyncTask가 Deprecated 되었습니다.</p>
<p>서버로부터 데이터를 가져오는 길고 긴 백그라운드 작업이 문제.</p>
<p>네트워크작업의 시간이 길경우 비동기적으로 처리한다해도 UI 가 예기치못하게 문제가 생겨 존재하지않거나
에러가 발생하여 충돌 또는 버그가 발생할 수 있는 근본적인 문제가있다.</p>
<p>하지만 AsyncTask는 전체적인 프로세스를 단순화 하지만 안드로이드의 생명주기를 신경쓰지 않는다.
그래서, 앞서말한 UI에 대한 내용이 보호되지 않는 단점이 있다.</p>
<h2 id="명령형-프로그래밍과-다르다">명령형 프로그래밍과 다르다?</h2>
<p>작성된 코드가 정해진 순서대로 실행됨 - 명령형 프로그래밍 </p>
<p>데이터 흐름을 먼저 정의하고 데이터가 변경되었을때 연관되는 함수나 메서드가 업데이트 - 리액티브 프로그래밍</p>
<p>그래서 RX가 나왔다.</p>
<p>대부분 ReactiveX를 사용. 즉 Rx종류들은 하나의 ReactiveX Extensions!
    RxJava: Java(JVM)를 위한 ReactiveX ExtensionsReactive programming(리액티브 프로그래밍) 패러다임을 자바에서 구현한 프로그래밍 라이브러리</p>
<pre><code>RxKotlin: Kotlin을 위한 ReactiveX ExtensionsRxJava 라이브러리를 기반으로 포팅하여 코틀린을 위한 리액티브 프로그래밍의 특정 부분을 함수형 프로그래밍으로써 구현한 라이브러리

RxAndroid: Android를 위한 ReactiveX ExtensionsRxJava에 최소한의 클래스를 추가하여 안드로이드 앱에서 리액티브 구성요소를 쉽고 간편하게 사용하게 만드는 라이브러리

RxSwift: Swift를 위한 ReactiveX Extensions</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[객체지향과 절차지향의 오해]]></title>
            <link>https://velog.io/@yunsung_/%EA%B0%9D%EC%B2%B4%EC%A7%80%ED%96%A5%EA%B3%BC-%EC%A0%88%EC%B0%A8%EC%A7%80%ED%96%A5%EC%9D%98-%EC%98%A4%ED%95%B4</link>
            <guid>https://velog.io/@yunsung_/%EA%B0%9D%EC%B2%B4%EC%A7%80%ED%96%A5%EA%B3%BC-%EC%A0%88%EC%B0%A8%EC%A7%80%ED%96%A5%EC%9D%98-%EC%98%A4%ED%95%B4</guid>
            <pubDate>Mon, 07 Mar 2022 11:06:54 GMT</pubDate>
            <description><![CDATA[<h1 id="하나하나씩-뜯어보면서-알아보자">하나하나씩 뜯어보면서 알아보자</h1>
<p><img src="https://images.velog.io/images/yunsung_/post/190c8b30-9896-46e3-8c76-a4a4792081b5/image.png" alt=""></p>
<p>위의 사진을 보자. 먼저 절차지향을 보면 순서대로 프로그램을 실행하는 프로그램임이 틀림없습니다. </p>
<p>그 다음으로 눈길이가는 객체지향을 확인해봅시다. </p>
<p>개별적으로 자주 참조되거나 호출되는 변수들을 분리하는 </p>
<p>함수들을 &#39;객체&#39;라는 틀로 묶어서 사용했다는 것은 둘째 치고, </p>
<p><code>순서대로 차근차근 진행되는 실행방식</code>에 있어서는 차이점이 없는 것 같습니다.</p>
<h1 id="문제점을-짚어보자-🔎">문제점을 짚어보자 🔎</h1>
<blockquote>
<p>평소에 우리는 객체지향에 대해 알아보려면 어떻게 하나요?</p>
</blockquote>
<p>네 맞습니다. <code>절차지향 방식이랑 비교</code>를 합니다. (마치 꼬리표처럼 말이죠.)➰</p>
<p>하지만 이상하지 않나요? 왜 객체지향을 공부할때는 항상 절차지향과 비교를 하는건가요?</p>
<blockquote>
<p>객체지향은 코드가 절차에 따르지않고 동시다발적 혹은 순서가 없다는말이 될까요?</p>
</blockquote>
<p><img src="https://images.velog.io/images/yunsung_/post/b8e72ca4-be2c-440e-babf-6688fa8e67ce/image.png" alt=""></p>
<h1 id="정확히-다시-알아보자-👀">정확히 다시 알아보자 👀</h1>
<p>우선 두개의 방식 모두 순서대로 차근차근 진행되는 실행방식에 있어서는 차이점이 없는 것 같습니다. </p>
<blockquote>
<p>그렇다면 <strong>왜!</strong> 대체 짝꿍처럼 객체지향에 대해 알아보려면 굳이 &quot;절차&quot;지향과 비교하는것일까요?</p>
</blockquote>
<p>사실 객체지향의 개념과 특징을 설명함에 있어서 굳이 절차지향과 비교할 필요는 없습니다. 
<img src="https://images.velog.io/images/yunsung_/post/369ac8de-c8b9-4e94-af0c-fb57e1c9aeac/image.png" alt=""></p>
<p><strong>객체</strong> 란 기존의 방식인 변수 따로, 함수 따로의 분산적이고 통일성 없는 추상화 과정을 통합하여 표현 대상(문제 해결 대상)을 좀 더 <code>모듈화 하기 쉽게 도와주는 도구</code>에 불과하고</p>
<p><strong>객체지향</strong> 프로그래밍은 객체의 디자인을 한 뒤에 이들의 데이터 플로우를 짜고 진행 시나리오를 <code>설계해나가는 방식의 개발 방법론</code>일 뿐이기 때문입니다.</p>
<p><code>플로우차트를 먼저짜느냐 데이터 모델링을 먼저 하느냐의 차이</code> 지 그 이후로는 절차지향 프로그램이나 객체지향 프로그램이나 다들 정해진 알고리즘을 따라 순서대로 실행되는 건 마찬가지 입니다.</p>
<p><del>사실 절차지향이라는 말이 햇깔리게 만드는 주범이 아닐까요? 😅</del></p>
<p>굳이 &quot;절차지향&quot;이라는 이름을 써야 한다면</p>
<blockquote>
<p>&quot;절차지향 프로그래밍은 프로그램의 순서와 흐름을 먼저 세우고 필요한 자료구조와 함수들을 설계하는 방식이고, 객체지향 프로그래밍은 반대로 자료구조와 이를 중심으로 한 모듈들을 먼저 설계한 다음에 이들의 실행 순서와 흐름을 짜는 방식이다&quot;</p>
</blockquote>
<p>정도의 설명을 추가하는 것만으로도 그렇게까지 헷갈릴 일은 없을 것입니다. 그게 아니면, </p>
<p>절차지향이라는 이름 대신 차라리 <code>&quot;비 객체지향 방식&quot;</code>이라고 쓰는 게 나을지도 모릅니다.</p>
<blockquote>
<p>알기 쉽도록! 🙋</p>
</blockquote>
<p><img src="https://images.velog.io/images/yunsung_/post/9fef0f16-1d43-45bd-8cce-bbbbef216eaf/image.png" alt=""></p>
<p>당신이 다음과 같은 레고성을 만들고 싶을 때 만드는 유형을 2가지로 나눌 수 있는데,</p>
<pre><code>- 설명서를 보고 레고 블록을 그때그때 그 순서에 맞게 조립하는 방법

- 성을 조립할 레고블록을 성의 머리부분, 성의 다리부분, 성의 내부부분을

  각 파트별로 미리조립 후 필요할 때 조립하는 방법</code></pre><p>와 같이 두 방법이 있는것이죠. 😁</p>
<h1 id="착각-바로잡기-📿">착각 바로잡기 📿</h1>
<p><img src="https://images.velog.io/images/yunsung_/post/ccc4c900-e3c9-4d38-bfaf-ee90d459cb09/image.png" alt=""></p>
<p>앞서 살펴본 자판기 프로그램 예시에서도 볼 수 있듯이, 여러 책에서는 절차지향과 객체지향을 
비교할 때 대부분이 절차지향은 <code>&quot;순서도&quot;</code> 로, 객체지향은 <code>&quot;클래스 다이어그램&quot;</code> 으로 도표를 그려서 비교해놔서 혼란을 가중시킵니다.</p>
<p>애당초 순서도와 클래스 다이어그램은 서로 용도부터 다른 도구입니다. </p>
<p>알다시피 순서도는 <code>알고리즘</code>을 표현하기 위한 도구이고, </p>
<p>클래스 다이어그램은 자료구조 간의 <code>상호관계</code>를 기술하기 위한 도구입니다.</p>
<p>객체지향 방식으로 설계하더라도 그 알고리즘을 표현할 때 당연히 순서도를 활용할 수 있으며, 반대로 절차식 방식으로 설계하더라도 클래스 다이어그램을 이용하여 데이터들과 모듈 간의 관계를 설명할 수 있습니다.</p>
<p>물론 확실히 절차식 프로그래밍에서는 거의 <code>순서도 위주</code>로, </p>
<p>반대로 객체지향 프로그래밍에서는 <code>*UML 위주</code>로 설계가 이루어지는 것은 엄연한 사실입니다.</p>
<p>그러나 클래스 다이어그램의 개념과 용도에 익숙하지 않은 상태에서 저 그림만 본다면 객체지향 프로그램은 모듈들이 <code>실시간으로 정보를 주고받고, 순서에 상관없이 동시다발적으로 실행된다</code> 는 착각을 심어줄 수 도 있습니다.</p>
<blockquote>
<p><strong>UML 이 대체 뭔데?</strong></p>
<p>쉽게 말하자면 시스템의 정적인 상태인 &#39;논리적인 구조&#39;를 표현합니다.</p>
<p>클래스간의 관계를 한눈에 파악하는게 주 목적입니다.</p>
</blockquote>
<h1 id="결론-🔨">결론 🔨</h1>
<p>객체지향은 기존의 방식에 비해 프로그램의 수행절차를 간소화해주지만, 절차를 무시하는 것은 결코 아닙니다.</p>
<p>깔끔한 모듈화를 통해서, 단 몇 줄의 메서드 호출만으로 구성된 메인 함수를 작성할 수도 있도록 도와주지만 수행절차가 간소화되어 코드가 대폭 줄었다고 할지라도</p>
<p>코드 각 부분의 실행 순서는 엄연히 존재하며, 근본적으로 모듈화라는 것은 그 함수가 호출될 때 참조되어 실행되어야 할 다량의 본체 코드가 존재하고 단지 이를 별도의 장소로 분리해서 <code>정리해놓는 기술</code>이라는 사실을 잊어서는 안 됩니다.</p>
<p>절차를 줄여주고 읽기에 깔끔한 프로그램으로 보이는 것은 순전히 사용자(개발자) 입장에서나 추상화를 통해 그렇게 느끼는 것이며,</p>
<p>시스템 내부적인 관점에서는 실제로 실행되는 코드의 양은 절차식이나 객체지향이나 큰 차이가 없으며 둘 다 한 번에 한 줄씩 <code>&#39;순서대로&#39; 실행시키는 것</code> 에는 다름이 없다는 것을 명확히 해야합니다.</p>
<p>집에 물건들을 정리한다고해서 물건자체의 질량이 줄어드는건 아닌것처럼 말이죠. 🤗</p>
<h1 id="번외">번외</h1>
<blockquote>
<p>UML 의 특징</p>
</blockquote>
<pre><code>- OOP는 유연성(Flexibility)을 가진다.

- 유연성은 캡슐화와 추상화, 다형성을 이용해서 달성된다.

- 상속은 캡슐화를 활용하고, 다형성은 상속을 이용해서 만들어진다.

- 캡슐화를 통해서 가시성(Visibility) 개념이 만들어지고, 클래스 개념이 만들어진다.

- 클래스는 타입과 필드, 그리고 메서드를 가지고 있다.

- 클래스 개념은 인터페이스(Interface), 추상 클래스(Abstract Class), 구체 클래스-(Concrete Class) 개념을 파생시킨다.

- 객체(Object)는 구체 클래스(ConcreteClass)가 가진 개념을 포함한다.

- 객체는 Identity를 가진다.(구체 클래스를 통해 클래스가 가진 개념도 가지게 되므로 Method, Field, Type도 가지게 된다.)

- 메서드는 프로토타입을 가지며, 추상 메서드는 메서드의 일종이다.

- 필드는 Reference와 Primitive로 나뉜다.

- 클래스, 메서드, 필드는 가시성을 가진다.

- 클래스는 상속 가능(Inheritable)하다.

- 메서드는 재정의 가능(Overridable)하고, 오버 로딩 가능(Overloadable)하다.
</code></pre><blockquote>
<p>객체지향 vs 절차지향</p>
</blockquote>
<pre><code>1. 객체지향은 절차를 간소화하는 것이지, 결코 절차를 무시하는 게 아니다.

2. 절차지향은 데이터와 함수가 분리되고 통일성이 없지만, 객체지향은 좀 더 모듈화 되어 체계적이다.

3. 절차지향은 과도한 전역 변수의 사용, 스파게티 소스, 변경과 확장, 프로그램에 대한 이해

    가 어렵지만, 객체지향은 코드의 재사용성이 높다.

4. 절차지향은 프로그램의 순서와 흐름을 먼저 세우고 필요한 자료구조와 함수들을 설계하는 방

    식이고, 객체지향은 반대로 자료구조와 이를 중심으로 한 모듈들을 먼저 설계한 다음에 이들의 

    실행 순서와 흐름을 짜는 방식이다.
</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[안드로이드 Looper & Handler 기초 개념]]></title>
            <link>https://velog.io/@yunsung_/Looper-Handler</link>
            <guid>https://velog.io/@yunsung_/Looper-Handler</guid>
            <pubDate>Thu, 03 Mar 2022 23:03:42 GMT</pubDate>
            <description><![CDATA[<h1 id="안드로이드의-ui-동작">안드로이드의 UI 동작</h1>
<p>안드로이드를 개발해본 사람들이라면 다들 알다시피, </p>
<p>안드로이드의 UI 처리는 싱글 쓰레드 모델로 동작한다.</p>
<p>즉, 메인 쓰레드가 아닌 다른 쓰레드에서 UI 를 업데이트하는 등의 행위를 하면 안된다. </p>
<p>따라서 메인 쓰레드를 <code>UI 쓰레드</code>라고 부르기도 한다.</p>
<h1 id="왜-ui-는-싱글-쓰레드-모델로-동작할까">왜 UI 는 싱글 쓰레드 모델로 동작할까?</h1>
<p>이유는 간단하면서도 당연하다. 멀티 쓰레드 환경이라고 가정했을 때, </p>
<p>여러 쓰레드에서 TextView 의 텍스트를 변경하는 상황이 발생하면 어떤 결과가 나타날 지 </p>
<p>예측하기 힘들기 때문이다. </p>
<p>즉, <code>UI 동작의 무결성</code>을 확보하기 위함이다.</p>
<p><strong>이러한 싱글 쓰레드 모델에서 지켜야할 포인트들</strong></p>
<blockquote>
<ol>
<li>``메인 쓰레드 (UI 스레드) 를 블로킹해서는 안 됨`
→ 메인 쓰레드를 블로킹한다는 뜻은, 사용자에게 보여지는 UI 동작을 멈춘다는 뜻이다. 메인 쓰레드가 블로킹되어 UI 동작이 멈추게 되면, 사용자는 멈춘 UI만 멀뚱멀뚱 쳐다볼것이다.
따라서, 시간이 오래 걸리는 동작을 수행하는 등 메인 쓰레드를 블로킹해선 안 된다.</li>
</ol>
</blockquote>
<blockquote>
<p>2<code>. UI 관련 동작은 오로지 메인 쓰레드에서만 접근해야 함</code>
→ 이유는 위에서 설명했다. UI 동작의 무결성을 보장하기 위함이다.</p>
</blockquote>
<h1 id="시간이-오래-걸리는-무거운-동작들은-따로-돌리자">시간이 오래 걸리는 무거운 동작들은 따로 돌리자</h1>
<p>즉, 무거운 동작들은 메인 쓰레드가 아닌 다른 쓰레드를 생성하여 수행해야 한다. </p>
<p>그런데, 어차피 쓰레드를 별도로 생성하여 시간이 오래 걸리는 동작들을 한다고 해도,</p>
<p>결국 그 동작의 &#39;결과&#39; 는 보통 UI 를 업데이트하는 데에 사용되기 때문에 </p>
<p>결코 메모리 낭비가 되지 않는다.</p>
<blockquote>
<p>예를 들어 쓰레드를 새로 생성하여, 귀여운 뽀로로의 사진을 제공해주는 서버의 API 를 호출하여 고양이 사진을 받은 뒤 ImageView 에 보여주는 동작을 한다고 하자.</p>
</blockquote>
<p>그런데 메인 스레드에서 모두 처리하게 되면 UI스레드를 블로킹하게된다. </p>
<p>또, 아까 별도의 쓰레드에선 UI 관련 동작을 해서는 안 된다고 했다. </p>
<p>그럼 결과로 받은 고양이 사진을 어떻게 사용자에게 보여줄 수 있을까?</p>
<p><img src="https://images.velog.io/images/yunsung_/post/603b4965-9112-4e4c-a973-f2842c5729ae/image.png" alt=""></p>
<p>가장 먼저 떠오르는 방법은, 다른 쓰레드에서 메인 쓰레드로 결과를 전송하는 방식이다. 즉, <strong>쓰레드간의 통신을 구현하는 것이다.</strong></p>
<p>안드로이드에선 쓰레드간의 통신을 위해, <code>Looper</code> 와 <code>Handler</code> 라는 장치를 제공해준다. 이 녀석들을 활용하여 효율적으로 멀티 쓰레딩 환경을 구축할 수 있다. 이번 포스팅에선 이 녀석들을 알아보고자 한다.</p>
<h1 id="looper">Looper</h1>
<p>하나의 쓰레드에는 오직 하나의 Looper 를 가지며, <code>Looper</code> 는 오직 하나의 쓰레드를 담당한다. 안드로이드에선 기본적으로 <code>MainActivity 가 실행됨과 동시</code>에 자동으로 메인 쓰레드의 <code>Looper</code> 가 돌기 시작한다.</p>
<p>각 쓰레드의 Looper 내부에는 <code>MessageQueue</code> 라는 것이 존재하는데,</p>
<p>여기에는 해당 쓰레드가 처리해야 할 동작들이 &#39;메세지&#39; 라는 형태로 하나씩 쌓이게 된다. </p>
<p><del>마치 회사에서 내가 맡은 일이 수북히 서류로 쌓이는 느낌으로 말이다.</del></p>
<p><img src="https://images.velog.io/images/yunsung_/post/69f101bf-dfe4-4dbe-b162-354aa866660c/image.png" alt=""></p>
<p><code>Looper</code> 는 궁극적으로, MessageQueue 에 들어오는 메세지들을 하나씩 꺼내어 이를 적절한 Handler 로 전달하는 역할을 한다.</p>
<blockquote>
<p>기본적으로 Looper 는 자신이 어떤 Handler 에 메세지를 전달해야 하는지에 대한 참조를 갖고 있다. (기본값 : 메인 쓰레드의 Handler)</p>
</blockquote>
<p>물론 Looper도 바보는 아니다.</p>
<p>MessageQueue 가 비어있을 땐 아무 동작을 수행하지 않는다.</p>
<p>(무한 루프를 돌면서 큐에 쌓여있는 메세지를 Handler 에 전달해주는 동작 특성 상 Looper 라는 이름이 딱 맞는 옷을 입은것 같다.)</p>
<p>특히, 메인 쓰레드의 <code>Looper</code> 는 보통 UI 작업을 위한 <code>메세지를 처리</code>하게 된다.</p>
<blockquote>
<p>메시지(Message)란?
Message 는 &#39;하나의 작은 작업 단위&#39; 라고 생각하면 편하다. <code>MessageQueue</code> 에는 이러한 작은 작업 단위를 하나씩 쌓아두고, <code>Looper</code> 가 이를 차례대로 처리하는 것이다.</p>
</blockquote>
<p><code>Message</code> 객체는 내용물이 두 가지 종류로 이루어진다. </p>
<ul>
<li><p><code>Runnable</code> 객체로 이루어져있을 수도 있다.</p>
</li>
<li><p>일반적인 경우 <code>Message</code> 객체로 이루어져있을 수도 있다.</p>
</li>
</ul>
<p><code>Looper</code> 객체가 메세지 큐에서 메세지를 하나를 딱 받았을 때, </p>
<pre><code>- Runnable 객체가 담겨져있을경우

Handler 에 메세지를 전달하지 않고 run() 을 수행하여 해당 Runnble 작업을 바로 시작한다.

- Runnable 객체가 없을 경우

Message 객체 내부에 명시돼있는 Handler 의 handleMessage() 를 수행하여 처리한다.
</code></pre><h1 id="handler">Handler</h1>
<p><code>Handler</code> 는 명칭에서 알 수 있듯 뭔가를 다루는 녀석인데, 두가지 역할을 하게된다.</p>
<ul>
<li>특정 메세지를 Looper 의 MessageQueue 에 <code>넣는 역할</code></li>
<li>Looper 가 MessageQueue 에서 특정 메세지를 <code>꺼내어 전달 하는역할</code></li>
</ul>
<p>즉, 중간 다리 역할이라 볼 수있는것이다.</p>
<h3 id="looper-로-메세지를-전달하는-경우">Looper 로 메세지를 전달하는 경우</h3>
<p>Message 객체를 생성하여 이를 <code>전달</code>하는 방식으로 구현한다.</p>
<pre><code>sendMessage() 메소드를 통해 메세지 큐에 Message 객체를 적재할 수 있다.

post 로 시작하는 메소드들을 통해 Runnable 객체를 직접 적재할 수 있다.</code></pre><h3 id="looper-로부터-메세지를-전달받는-경우">Looper 로부터 메세지를 전달받는 경우</h3>
<pre><code>- Runnable 객체가 담겨져있을경우

Handler 에 메세지를 전달하지 않고 run() 을 수행하여 해당 Runnble 작업을 바로 시작한다.

- Runnable 객체가 없을 경우

Message 객체 내부에 명시돼있는 Handler 의 handleMessage() 를 수행하여 처리한다.
</code></pre><h1 id="전반적인-동작-흐름">전반적인 동작 흐름</h1>
<p><img src="https://images.velog.io/images/yunsung_/post/48840ee3-b480-4140-b4c0-bf712c539e44/image.png" alt=""></p>
<ul>
<li><p>다른 쓰레드에서 특정 쓰레드 <code>Handler</code> 의 <code>sendMessage()</code> 를 활용하여 메인 쓰레드 <code>Looper</code> 의 MessageQueue 에 메세지를 전달함</p>
</li>
<li><p>해당 쓰레드의 Looper 는 <code>MessageQueue</code> 에서 loop() 를 통해, 메세지를 하나씩 Handler 에 전달함</p>
</li>
<li><p><code>Handler</code> 에서<code>handleMessage()</code> 를 통해 메세지 처리함</p>
</li>
</ul>
<blockquote>
<p>Handler는 바보다?</p>
</blockquote>
<blockquote>
<p>MessageQueue 와, MessageQueue 안의 메세지들을 자신에게 전달해주는 Looper 에 의존적인 녀석임을 알 수 있다. Looper 가 없다면 아무것도 못하는 녀석이다.</p>
</blockquote>
<h1 id="사용해보기-in-kotlin">사용해보기 in kotlin</h1>
<p>자, 그럼 <code>Handler</code> 를 생성해보자. <code>Handler</code> 는 <code>Looper</code> 와 <code>MessageQueue</code> 가 있어야 하는 상당히 <code>의존</code>적인 녀석이기 때문에, 무조건 <code>Looper</code> 가 필요하다.</p>
<pre><code>var handler: Handler? = null
val thread = Thread {  // Runnable 익명 객체 구현

    Looper.prepare()   // ---ㅣ
    handler = Handler() // --ㅣ 1.

    Looper.loop() // --ㅣ 2.
}
thread.start()

//하지만, 이렇게 Looper 를 생성하면 Handler 가 암시적으로 Looper 를 선택하게 되는
데, 이 과정에서 특정 작업이 손실되거나 충돌하는 등의 버그가 발생할 수 있어 해당 방식은
deprecated 되었다.</code></pre><p>위의 코드는 총 2개의 단계로 나뉠 수 있는데,</p>
<ol>
<li><p><code>Looper.prepare()</code> 를 통해 해당 쓰레드에 종속되는 <code>Looper</code> 와 <code>essageQueue</code>를 준비해주고, <code>Handler</code> 를 생성해준다. (이 순간 <code>Handler</code> 와 <code>Looper</code> 가 연결된다.)</p>
</li>
<li><p>해당 쓰레드가 익명으로 구현한 <code>Runnable</code> 객체의 run() 메소드 마지막에서 <code>Looper.loop()</code> 를 호출해줌으로써 Message 전달을 기다리는 동작을 시작한다.</p>
</li>
</ol>
<p>또한, 안드로이드는 연결할 Looper 를 명시하여 Handler 를 생성하는 방법을 <code>권장</code>한다.</p>
<p>보통 메인 쓰레드와의 통신을 필요로 하기 때문에, 이번 포스팅에선 <code>메인 쓰레드와 외부 쓰레드 의 통신</code>을 다뤄보겠다.</p>
<h1 id="main-looper-명시하여-handler-생성하기">Main Looper 명시하여 Handler 생성하기</h1>
<p>안드로이드에선 별도 쓰레드의 결과를 보통 메인 쓰레드에서 처리하기 때문에,</p>
<p>아래와 같이 메인 쓰레드가 갖고 있는 <code>Looper</code> 를 명시하여 <code>Handler</code> 를 생성하면 된다.</p>
<p>이렇게 구현하면 메인 쓰레드의 <code>Message Queue</code> 에 메세지가 쌓이게 되고, 이를 메인 쓰레드의 <code>Looper</code> 가 하나씩 꺼내보게 된다. 또한 해당 <code>Handler</code> 는 메인 쓰레드의 <code>Looper</code> 를 명시하여 생성되었기 때문에, UI 관련 작업이 가능하다.</p>
<pre><code>
var handler: Handler? = null
val thread = Thread {  // Runnable 익명 객체 구현
    handler = Handler(Looper.getMainLooper())

    // handler 안에 Looper를 명시적으로 넣어주기 때문에 Handler 가 
    암시적으로 Looper 를 선택하게 돼서 일어나는 일이 해결이 된다.

}
thread.start()
</code></pre><p>해당 포스팅에선 <code>Handler 와 Looper 의 개념</code>에 대해서만 간략히 다뤄보았다. </p>
<p>안드로이드에서 멀티 쓰레딩 환경을 완벽히 구현하기 위해서는, </p>
<p><code>Handler 와 Looper</code> 에 대해서 자세하게 알고 있어야 한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[ Pages must fill the whole ViewPager2]]></title>
            <link>https://velog.io/@yunsung_/Pages-must-fill-the-whole-ViewPager2</link>
            <guid>https://velog.io/@yunsung_/Pages-must-fill-the-whole-ViewPager2</guid>
            <pubDate>Thu, 30 Dec 2021 11:21:38 GMT</pubDate>
            <description><![CDATA[<h2 id="error">Error!!</h2>
<p><strong>ViewPager2 를 사용하던 중 오류가 발생하였다</strong></p>
<h2 id="clear">Clear!!</h2>
<p><strong>ViewPager는 해당 ViewPager에 들어가는 item 이  layout_width 와 layout_height 모두 matchparent 가 되어야한다.</strong></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Binding Adapter 에 대해 알아보자!]]></title>
            <link>https://velog.io/@yunsung_/Binding-Adapter-%EC%97%90-%EB%8C%80%ED%95%B4-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90</link>
            <guid>https://velog.io/@yunsung_/Binding-Adapter-%EC%97%90-%EB%8C%80%ED%95%B4-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90</guid>
            <pubDate>Sun, 12 Sep 2021 09:08:43 GMT</pubDate>
            <description><![CDATA[<h2 id="before-start_">before start_</h2>
<p>안드로이드 xml을 짜다 문득 건들고 싶은 뷰의 속성이 xml 메서드에 없는것을 보았다.
그래서 엑티비티에서 내가 원하는 메서드를 만들어 사용하듯이 xml에서도 사용할 수 있는 속성 설정을 만들어 사용할 수있는 방법에 대해 찾아보다, binding Adapter 라는 속성을 발견하게 되었다. 그래서 오늘은 Binding Adapter에 대해 알아보도록 하겠다.</p>
<p><img src="https://jjalbang.today/jjv1HQ.gif" alt=""></p>
<h2 id="binding-adapter_">Binding Adapter_</h2>
<h3 id="1-사용-방법">1. 사용 방법</h3>
<pre><code>object TestBindingAdapter {
    @JvmStatic
    @BindingAdapter(&quot;this_is_binding_text&quot;)
    fun setText(view: TextView, text: String){
        view.text = text + &quot;\n&quot; + &quot;하지만 바인딩 어뎁터로 바뀌었습니다!&quot;
    }
}</code></pre><ul>
<li><p>object: Binding Adapter는 메모리상에 올려서 사용해야 하기 때문에 Object로 생성한다.</p>
</li>
<li><p>@JvmStatic: 전역 변수의 Getter Setter를 정적 함수로 설정하는 어노테이션이다.</p>
</li>
<li><p>@BindingAdapter: 괄호 안에 원하는 메서드 이름을 지어주면 된다.</p>
</li>
<li><p>setText: 이 메서드 이름도 원하는 걸로 정해주면 된다. 텍스트 뷰를 수정하는 기본 메서드 이름이 setText라고 해서 꼭 그걸 따라 사용하지 않아도 된다. (오버 라이딩 아님)</p>
</li>
</ul>
<p>다음은 바인딩 어뎁터의 필수 속성이다. 또한 setText 메서드 안에서 &#39;view.&#39; 까지만 입력해도 자신이 커스텀 할 수 있는 기능들이 다양하게 나타난다. 여기서는 사용자가 원하는 메서드를 이용해 원하는 기능을 넣어주면 된다.</p>
<blockquote>
<p>추가적으로 어노테이션에 대해 알고싶다면 다음 블로그를 참고하자!
<a href="https://jaejong.tistory.com/106">날고싶은개발자님 블로그</a></p>
</blockquote>
<h3 id="2-layout">2. Layout</h3>
<pre><code>&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;layout xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    xmlns:app=&quot;http://schemas.android.com/apk/res-auto&quot;
    xmlns:tools=&quot;http://schemas.android.com/tools%22%3E

    &lt;data&gt;
        &lt;variable
            name=&quot;activity&quot;
            type=&quot;com.example.selfstudy_kotlin.MainActivity&quot; /&gt;
    &lt;/data&gt;

    &lt;androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width=&quot;match_parent&quot;
        android:layout_height=&quot;match_parent&quot;
        tools:context=&quot;.MainActivity&quot;&gt;

        &lt;TextView
            android:id=&quot;@+id/txt_number&quot;
            android:layout_width=&quot;wrap_content&quot;
            android:layout_height=&quot;wrap_content&quot;
            android:textSize=&quot;24sp&quot;
            app:layout_constraintBottom_toBottomOf=&quot;parent&quot;
            app:layout_constraintEnd_toEndOf=&quot;parent&quot;
            app:layout_constraintStart_toStartOf=&quot;parent&quot;
            app:layout_constraintTop_toTopOf=&quot;parent&quot;

            &lt;--인자 값으로 activity에 있는 introduce라는 변수를 넘겨준다.--&gt;

            this_is_binding_text=&quot;@{activity.introduce}&quot;/&gt; 
    &lt;/androidx.constraintlayout.widget.ConstraintLayout&gt;
&lt;/layout&gt;</code></pre><p>레이아웃에서는 다음과 같이 사용할 수있다. 이때 앞에 &#39;android:&#39; 나 &#39;app:&#39;을 붙이지 않아도 된다. </p>
<h3 id="3-activity">3. Activity</h3>
<pre><code>class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding
    var introduce = &quot;안녕하세요. 저는 일반적인 텍스트 입니다.&quot;

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
        binding.activity = this
    }
}</code></pre><p>대충.. 바인딩을 배웠으면 알거라 믿지만 모르는 사람들을 위해 설명하자면 바인딩을<code>private lateinit var binding: ActivityMainBinding</code>로 받아와주고 다음과 같이 <code>binding = DataBindingUtil.setContentView(this, R.layout.activity_main)</code> 로 초기화를 시켜준다. 그리고 현재 엑티비티의 객체를 <code>binding.activity = this</code> 로 넘겨주는 코드이다.</p>
<h3 id="4-binding-adapter-왜필요한건데">4. Binding Adapter! 왜필요한건데?</h3>
<p><img src="https://images.velog.io/images/yunsung_/post/4dc91f15-9edd-4122-9b58-200cc1293de1/image.png" alt=""></p>
<p>단순히 뷰에 사용할 메서드를 직접 자신이 원하는대로 커스텀 할 수있다는 사실만으론 <code>binding Adapter</code> 를 써야하는 이유가 크게 와닫지않았다. 또한 방금의 코드들은 엑티비티에서 처리하게 하면 더욱 짧은 코드로 변환 할 수있었다.</p>
<p>그래도 굳이 쓰는 이유를 곰곰히 생각해 보았더니.. 역시 핵심은 <code>역할의 분리</code> 인것 같았다.</p>
<p>엑티비티에서 물론 처리해도 되지만 엑티비티에서 모든 로직을 처리하게 된다면 나중에는 엄청난 스파게티코드와 덤으로 엄청난 유지보수까지 ㅎㅎ.. 겪게 될것이다. 따라서 각자의 역할과 기능을 가지는 모듈로 잘게 쪼개서 보다 가시성도 늘리고 MVVM, ViewModel, Data Binding 등을 이용해 클린코드를 만들려고 사용하는 느낌인것 같다. </p>
<h2 id="result_">result_</h2>
<pre><code>안녕하세요. 저는 일반적인 텍스트 입니다.


               ⬇️          


안녕하세요. 저는 일반적인 텍스트 입니다.

하지만 바인딩 어뎁터로 바뀌었습니다!</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[HiltViewModel 이해하기]]></title>
            <link>https://velog.io/@yunsung_/HiltViewModel-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@yunsung_/HiltViewModel-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sat, 11 Sep 2021 14:09:38 GMT</pubDate>
            <description><![CDATA[<h2 id="overview_">Overview_</h2>
<p>안드로이드 앱 개발시 뷰 모델을 구현할 때, 액티비티 또는 프래그먼트의 구성 변경에도
데이터가 유지되는 <strong>AAC-ViewModel🔎</strong> 클래스를 상속하는것을 권장합니다. <strong>AAC-ViewModel🔎</strong>을 사용할 때 생명주기에 맞춰 데이터를 유지, 보존하기위해 프로그래머는 <strong>ViewModelProvider.Factory🏭</strong> 를 구현하고 <strong>ViewModelProvider🤲</strong> 클래스가 뷰모델 인스턴스를 관리하도록 해야합니다.</p>
<p>하.지.만! </p>
<p>매번 ViewModelProvider.Factory 를 구현하려면 많은 중복 코드가 발생할 수 밖에없는데, 기존에는 이러한 중복코드를 막을 방법이 없었지만.. <code>Dagger2, Koin</code>과 같은 의존성 주입 라이브러리를 통해 비교적 편리하게 사용할 수 있게 되었습니다! 🎉</p>
<p>사실 Dagger라는 라이브러리도... 좋긴좋지만 복잡한 어노테이션, 높은 러닝커브 때문에 불편함이 많았던건 사실이라고 합니다...</p>
<p>그.래.서 </p>
<p><code>Dagger Hilt</code> 라는 라이브러리가 새로나오면서 적은 코드만으로 뷰모델 주입이 가능해졌습니다!(🎉🎉 이건 두번폭죽감이지ㅋㅋ)</p>
<p>이러한 배경을 보다가 최근 Dagger Hilt 공부를 시작하게되었는데 코드가 어떻게 작동하는지 문득 궁금해져서 공부겸 기록으로 남기려고 동작 원리에 대한 글을 쓰게 되었습니다.</p>
<h2 id="before-entering_">Before Entering_</h2>
<h3 id="multibindings">Multibindings</h3>
<p>우선 먼저 알아야하는 기능이 있습니다. 바로바로! ``Multibindings🎉`</p>
<blockquote>
<p><strong>멀티바인딩이란?</strong>
@IntoMap 어노테이션이 지정된 모든 주입 대상 객체를 컴포넌트 내부에 선언된 Map 인스턴스에 모아주는 기능입니다!</p>
</blockquote>
<p>우리는 이러한 바인딩을 기존 Dagger2 에서도 지원하는 기능인 멀티바인딩을 활용하여ViewModelProvider.Factory를 매번 구현하지 않고도 사용이 가능했습니다.</p>
<p><strong>ex)</strong>
<img src="https://images.velog.io/images/yunsung_/post/8278261a-3b0f-4601-92a4-33d716d2d6c3/image.png" alt="">이러한 클래스 묶음을</p>
<p><img src="https://images.velog.io/images/yunsung_/post/0871d2ba-3588-49fc-9d75-4c7cf395e600/image.png" alt=""></p>
<p>다음과같이 어노테이션이 해당되는클래스의 함수를 Map으로 묶어주는 느낌!</p>
<h2 id="기존-dagger2-에서의-뷰모델-생성-방식">기존 Dagger2 에서의 뷰모델 생성 방식</h2>
<p><img src="https://images.velog.io/images/yunsung_/post/f0868d6a-22fd-4fe9-834e-f64a2a127e0a/image.png" alt=""></p>
<ol>
<li>Dagger2 에서 멀티바인딩 기능을 사용하여 뷰모델을 식별해줄 Key 와 ViewModel 객체를 모듈에 선언합니다.</li>
<li>ViewModelProvder.Factory 를 상속받은 뷰모델팩토리(ViewModelFactory) 클래스를 만들어서 대거를 통해 주입받은 map(creators)에서 뷰모델 key 로 조회하여 뷰모델을 생성하도록 구현</li>
<li>액티비티에서는 viewModelFactory 를 주입 받아서 뷰모델을 생성!</li>
</ol>
<h3 id="개선된점">개선된점</h3>
<p>Dagger2 를 사용 했을 때는 뷰모델 클래스가 추가될 때마다 매번 viewModelFactory 를 만들어주어야 하는 불편함은 개선 되었지만, 멀티바인딩 기능에 대한 학습이 필요하고 의존성 주입을 하기 위해 필수적으로 구현해야 하는 모듈이 많았습니다.</p>
<h2 id="dagger-hilt-에서의-뷰모델-생성방식">Dagger Hilt 에서의 뷰모델 생성방식</h2>
<p><img src="https://images.velog.io/images/yunsung_/post/de41ca12-00ba-4822-872d-148c3efa3656/image.png" alt=""></p>
<p>1.액티비티에서는 @AndroidEntryPoint 어노테이션을 읽어서 Hilt_XXXActivity 를 생성합니다
2.Hilt_XXXActivity 내부에서는 getDefaultViewModelProviderFactory() 메서드를 오버라이드하여 앞서 Dagger2 를 사용할 때 직접 만들었던 ViewModelFactory 와 동일한 역할을 수행하기 위한 HiltViewModelFactory 를 반환하도록 합니다
3.activity-ktx 라이브러리에 선언된 확장함수를 이용하여 간단하게 AAC-ViewModel 을 생성하고 사용할 수 있게 되었습니다.</p>
<h2 id="conclusion_">Conclusion_</h2>
<p>Dagger Hilt 에서 개발자가 직접 구현했어야 했던 모듈, 컴포넌트를 모두 미리 생성해주어서 간단하게 DI 을 할 수 있게 되었습니다!!🎉</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[MVC 패턴에 대해 알아보자]]></title>
            <link>https://velog.io/@yunsung_/%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4-MVC-%ED%8C%A8%ED%84%B4%EC%97%90-%EB%8C%80%ED%95%B4-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90</link>
            <guid>https://velog.io/@yunsung_/%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4-MVC-%ED%8C%A8%ED%84%B4%EC%97%90-%EB%8C%80%ED%95%B4-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90</guid>
            <pubDate>Mon, 23 Aug 2021 08:02:08 GMT</pubDate>
            <description><![CDATA[<h2 id="시작에-앞서">시작에 앞서</h2>
<p>나는 개발을 하다가 종종 전에 코드를 짯던 프로젝트를 들어간다.</p>
<pre><code>내가 전에 짯던 코드를 보면 어떤방식으로 내가 어떻게 해결했나 하는 기억이 나기 때문이다.🔍</code></pre><p>   프로젝트를 들어가서 많은걸 느끼는데,  전에 내가 이런 문제를 해결했다니 하는 감탄도 나오지만 한편으로는 맛있는 스파게티(?) 코드가 보인다.
   <img src="https://media.giphy.com/media/iJJ6E58EttmFqgLo96/giphy.gif" alt=""></p>
<pre><code>                  나도 클린코드짜고싶어..</code></pre><p> 그래서 자연스럽게 <strong>클린코드(clean code)</strong> 에 눈을 들이게 되었고 그대로 공부를
 해보았으나 공부를 하다가 생각해보니 개념만 머리속에서 빙빙돌고 정확한 정리를 한 후
 써보는게 좋겠다 생각해서 디자인 패턴 개념 정리를 시리즈로 해보려 한다.</p>
<p><del>사실 면접때 이런 질문이 나올거같기도해서..</del></p>
<pre><code>앞으로 클린코드를 써, 유지 보수에 대해 민감하게 반응 할 수 있는 개발자가 되기로 하자!</code></pre><h2 id="그래서-디자인-패턴-🎨-이-뭔데">그래서 디자인 패턴 🎨 이 뭔데?</h2>
<blockquote>
<p>소프트웨어 개발 방법에서 사용되는 디자인 패턴은, 프로그램 개발에서 자주 나타나는 과제를 해결하기 위한 방법 중 하나로, 과거의 소프트웨어 개발 과정에서 발견된 설계의 노하우를 축적하여 이름을 붙여, 이후에 재이용하기 좋은 형태로 특정의 규약을 묶어서 정리한 것이다. 알고리즘과 같이 프로그램 코드로 바로 변환될 수 있는 형태는 아니지만, 특정한 상황에서 구조적인 문제를 해결하는 방식을 설명해 준다. 
<a href="https://ko.wikipedia.org/wiki/%EB%94%94%EC%9E%90%EC%9D%B8_%ED%8C%A8%ED%84%B4">위키피디아</a></p>
</blockquote>
<p>다음과 같이 디자인 패턴의 정의는 쉽게말해서 건축물을 지을 때 쓰는 건축 공법같은것이다.
<img src="https://images.velog.io/images/yunsung_/post/fcbf1fd8-3b17-4bae-8bfa-fa53157ef19e/image.png" alt="">
굳이 지키지 않아도 무방하지만 지키게되면 더 좋은 결과를 낼 수 있다.</p>
<p>안드로이드에서의 디자인 패턴을 코딩에 사용하게되면 의해 코드도 보기 좋게 작성이 됨과 동시에 
<strong>유닛 테스트</strong> 및 <strong>유지보수</strong> 또한 편해진다.</p>
<p>아무것도 모르는 초보시절에는 계산기, 일기장, 메모장과같은 디자인 패턴 없이 할 수 있는 간단한 앱을 만들때에는 별로 디자인패턴의 유용함을 느끼지 못하지만 실제 서비스 사용량이 많거나 더욱 복잡한 로직의 앱을 개발하다 보면 시간이 흐르면서 사용자가 원하는 요구사항이 더 생기고 처음보다 더 복잡한 기획이 생기고 유지 보수할 일이 생긴다. </p>
<pre><code>(실제로 나도 겪고 있는 현상이다.)</code></pre><blockquote>
<p>만약 이러한 디자인 패턴 없이 개발을 진행했다면?</p>
</blockquote>
<p>   변경이 될 때마다 화면을 구성하는 코드와 비즈니스 로직이 들어간 코드를 각각 동시에 매번 수정을 해야 한다..😅</p>
<p> 이러한 문제는 결국 서로 간의 의존성이 강해짐에 따라 유지보수를 하기가 힘들어 진다는것을 의미한다.</p>
<p>이러한 문제들을 해결하기 위해 디자인 패턴이 나오게 되었으며 <strong>Model-View 사이의 관계를 어떻게 해결해 나가냐</strong>에 따라 여러 패턴으로 갈려진다.</p>
<h2 id="그래서-내가-등장했지-mvc">그래서 내가 등장했지! MVC!</h2>
<p><img src="https://images.velog.io/images/yunsung_/post/5aa67e9c-478c-49f5-90c2-a8362dd517fc/image.png" alt=""></p>
<p> MVC는 안드로이드와 관계없이 프로그래밍 시 가장 널리 사용되는 구조 중 하나이며 
 간단하게 <strong>Model, View, Control</strong> 의 약자이다.</p>
<p> MVC 구조에서의 입력은 모두 Control에서 발생하게 되며 관리되게 되는 구조이다.
이벤트가 발생한 Control에 의해 각 모듈의 정의와 View의 사용 용도가 달라지게 된다.</p>
<h3 id="mvc-구조">MVC 구조</h3>
<ul>
<li><p><strong>모델(Model)</strong>
데이터 + 상태 + 비즈니스 로직 입니다. 말하자면 앱의 두뇌 역할! 
뷰나 컨트롤러에 묶이지 않으므로 많은 곳에서 재사용할 수 있다.</p>
</li>
<li><p><strong>뷰(View)</strong>
뷰는 모델의 표현 입니다. UI를 그리고 사용자가 앱과 상호작용할 때 컨트롤러와 통신하는 책임을 맡습니다. MVC 구조에서 뷰는 하위 모델에 대한 지식이나 상태에 대한 이해가 없고, 사용자가 버튼을 클릭하거나 값을 입력하는 등의 행동을 할 때 무엇을 해야 하는지 모른다는 점에서 상당히 “멍청합니다”. 그 이유는 뷰가 덜 알수록 모델에 종속되지 않으므로 보다 변화에 유연할 수 있기 때문이죠.</p>
</li>
<li><p><strong>컨트롤러(Controller)</strong>
컨트롤러는 앱을 묶어주는 접착제 입니다. 애플리케이션에서 발생하는 일을 담당하는 마스터 컨트롤러 역할이죠. 뷰가 컨트롤러에게 사용자가 버튼을 눌렀다고 알리면, 컨트롤러는 그에 따라 어떻게 모델과 상호작용할지 결정합니다. 모델에서 데이터가 변화되는 것에 따라 컨트롤러는 뷰의 상태를 적절하게 업데이트하도록 결정할 수 있습니다. 안드로이드 앱에서는 컨트롤러가 주로 <strong>액티비티나 프래그먼트</strong>로 표현됩니다.</p>
<p><img src="https://images.velog.io/images/yunsung_/post/9516cb6d-ab54-4126-ab97-1abf226bd586/image.png" alt=""></p>
</li>
</ul>
<h2 id="mvc의-장점">MVC의 장점</h2>
<ul>
<li>Model과 View의 분리됨.</li>
<li>Model의 비종속성으로 재사용 가능함.</li>
<li>구현하기 가장 쉽고 단순함.</li>
<li>유닛테스트에서 View는 테스트 할 부분이 없기 때문에 쉽게 Model만 테스트 가능.</li>
<li>개발자라면 누구나 쉽게 파악 가능함.</li>
<li>개발기간이 짧아짐. (안드로이드에서의 장점)</li>
<li>그냥 다른거 생각할것 없이 안드로이드 액티비티에서 모든 걸 다 동작하게 처리만 잘 해주면 개발 기간이 짧아질수도 있다.<h2 id="mvc의-단점">MVC의 단점</h2>
</li>
<li>Model과 View사이에 의존성 발생함. (서로간의 의존성 완전히 없앨 수 없음)
즉, View의 UI 갱신을 위해 Model을 직/간접적으로 참조하므로 앱 자체가 커지고 로직이 복잡해질수록 유지보수가 힘들어집니다.</li>
<li>스파게티 코드가 될 가능성이 높음.</li>
<li>코드 복사/붙여넣기가 많아지게 되면서 코드 분리조차 되지않으면 코드가 아주 제대로 꼬여버립니다. 그렇기에 복잡도는 증가합니다. 다만, 이는 설계 단계에서 제대로해서 분리를 잘하면 어느정도 해소는 됩니다.</li>
<li>시간이 지날수록 컨트롤러에 많은 코드가 쌓여 코드가 비대화하여 문제 발생 가능
Controller가 안드로이드 API에 깊게 종속되므로 유닛 테스트가 어려움.</li>
</ul>
<p>이렇게 오늘은 디자인 패턴의 개념과 MVC를 알아보았다! 다음시간에는 MVP 패턴에 대해 알아보겠다.</p>
<hr>
<p>출처 : <a href="https://velog.io/@jojo_devstory/%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-%EC%95%84%ED%82%A4%ED%85%8D%EC%B3%90-%ED%8C%A8%ED%84%B4-MVC%EA%B0%80-%EB%AD%98%EA%B9%8C">안드로이드 MVC</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[ViewModel 뽀개기❗️]]></title>
            <link>https://velog.io/@yunsung_/ViewModel-%EB%BD%80%EA%B0%9C%EA%B8%B0</link>
            <guid>https://velog.io/@yunsung_/ViewModel-%EB%BD%80%EA%B0%9C%EA%B8%B0</guid>
            <pubDate>Mon, 12 Jul 2021 02:28:17 GMT</pubDate>
            <description><![CDATA[<h2 id="viewmodel-그게-뭔데-💁">ViewModel 그게 뭔데? 💁</h2>
<p>특정 액티비티 화면과 연동되며, 해당 화면에 보여줄 데이터를 형식화하는 로직을 두는곳.
화면에서 필요한 모든 데이터를 &quot;종합&quot; 하고 &quot;형식화&quot; 할 수 있는것이다.</p>
<blockquote>
<p>즉,생명주기에 영향을 받지않고 값을 저장 할 수 있는곳이다!</p>
</blockquote>
<h2 id="근데-왜-why-필요해❓">근데 왜 (Why?) 필요해❓</h2>
<p>화면이 로테이션 되어 엑티비티가 재실행 되거나 잠시 앱을 나가고 다시 실행할 경우해당 엑티비티의 리소스는 제거하고 다시 새로운 엑티비티의 리소스를 가져오게됩니다.</p>
<p>그래서..</p>
<p>우리는 생명주기가 변경되어도 저장되어야 하는 값은 저장시켜주어야합니다. 하지만 화면이 변경될 때 마다 저장을 일일이 시켜주기란.. 쉽지않습니다. 코드도 일일히 작성해야하고 무엇보다 리소스를 소모하게되어 좋지 않을 수 있습니다.</p>
<p>그.래.서!</p>
<p>우리는 <strong>ViewModel</strong> 을 사용하게 되었습니다! 왜냐하면 ViewModel 객체는 엑티비티의 Lifecycle 상태가 종료(FINISH)상태가 될 때 까지 소멸되지 않습니다. 즉, 화면이 위와같은 이유로 재실행되어도 ViewModel은 데이터를 앱이 Finished 가 되기전 까지 유지하고있습니다.</p>
<p><img src="https://images.velog.io/images/yunsung_/post/46919603-8db4-402a-a386-2eeab5217160/image.png" alt=""></p>
<h2 id="좋아-그럼-사용하는방법은-🧾">좋아! 그럼 사용하는방법은? 🧾</h2>
<h3 id="우선-문제가-있는-앱을-참고해봅시다">우선 문제가 있는 앱을 참고해봅시다!</h3>
<p><img src="https://images.velog.io/images/yunsung_/post/0e4e4788-4e6b-400d-befd-d118aca9280b/image.png" alt=""></p>
<p>다음과 같은 심플한 타이머를 만들어 보았습니다. 이 앱의 문제점은?</p>
<blockquote>
<p>회전을 하면 타이머가 다시 0부터 시작해요!!</p>
</blockquote>
<p><img src="https://images.velog.io/images/yunsung_/post/d5e183a4-ef2e-4cd7-9b59-e0a28d7e0c48/image.png" alt="">
(Rotation되면서 Configuration Change가 발생하였고 액티비티가 재실행되어 다시 0부터 시작하는 모습)</p>
<p>그래서 우린 이 오류를 <strong>ViewModel</strong> 로 고쳐볼겁니다.</p>
<h3 id="viewmodel로-문제를-해결해봅시다🥸">ViewModel로 문제를 해결해봅시다!🥸</h3>
<h4 id="우선-viewmodel을-작성해주겠습니다">우선 ViewModel을 작성해주겠습니다!</h4>
<p><img src="https://images.velog.io/images/yunsung_/post/392d4579-cd95-4c64-b6f7-fd397208e1b6/image.png" alt=""></p>
<h4 id="그-다음-mainactivity의-코드를-작성해-주겠습니다">그 다음 MainActivity의 코드를 작성해 주겠습니다!</h4>
<p><img src="https://images.velog.io/images/yunsung_/post/11304dd3-6453-4b45-bbb7-71679091149f/image.png" alt=""></p>
<p>ViewModelProviders로 ViewModel의 객체를 생성해주고 그걸 이용해 countsave()로 ViewModel에 값을 저장하고 getcount로 값을 받아와 타이머에 넣어주어 타이머가 작동되게 해 주었습니다. 이제 만약 액티비티가 재실행된다고 해도 ViewModel은 소멸되지 않고 이전에 생성한 것을 사용하기 때문에 startTime은 처음에 설정된 값을 갖고 있습니다.</p>
<p>다음과 같이 하면??!?</p>
<p><img src="https://images.velog.io/images/yunsung_/post/e1860486-3927-4800-8476-ac56716898af/image.png" alt=""></p>
<p>쨘! 🎉 다음과 같이 화면을 돌려도 값이 변하지않는것을 알 수 있습니다.</p>
<h2 id="tmi추가-정보들--🦔">TMI(추가 정보들)  🦔</h2>
<h4 id="viewmodel에-대해-조금더-알아볼까요-🔍">ViewModel에 대해 조금더 알아볼까요? 🔍</h4>
<ol>
<li><p>ViewModelProVider (요청) =&gt; ViewModel (객체반환)</p>
</li>
<li><p>장치구성이 변경되어도 ViewModel을 다시 불러오는것이 아닌 최초로딩이된것을 가져오고 MainActivity의  상태에따라 그대로 변화한다.</p>
</li>
<li><p>단방향! MainActivity 는  ViewModel을 참조하지만 ViewModel은 MainActivity를 참조하지 않는다.</p>
</li>
<li><p>만약 ViewModel에서 Context나 Activity 객체를 사용하고 싶다면 ViewModel을 사용하지 말고 AndroidViewModel를 사용해야 합니다.</p>
</li>
</ol>
<p><strong>주의할점!</strong></p>
<ul>
<li><p>소멸되어야 하는 객체의 참조를 다른 객체가 참조하면 메모리 유실이 생길 수 있다! - 강한참조</p>
</li>
<li><p>현재 사용되지 않는 과거 액티비티의 참조를 가지게되어 ViewModel 인스턴스가 과거 액티비티의 뷰를 변경하려고 하면 오류발생!</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Room DataBase 뽀개기! ]]></title>
            <link>https://velog.io/@yunsung_/Room-DataBase-%EB%BD%80%EA%B0%9C%EA%B8%B0</link>
            <guid>https://velog.io/@yunsung_/Room-DataBase-%EB%BD%80%EA%B0%9C%EA%B8%B0</guid>
            <pubDate>Wed, 16 Jun 2021 07:28:41 GMT</pubDate>
            <description><![CDATA[<h2 id="room-너-정체가-뭐야-🧐">Room! 너 정체가 뭐야? 🧐</h2>
<h3 id="1sqlite-➡️-room">1.SQLite ➡️ Room</h3>
<p>Room은 스마트폰 내장 DB에 데이터를 저장하기 위해 사용하는 라이브러리 입니다!
우리가 평소에 사용하는앱에도 Room이 적용되어 있는경우가 많은데 </p>
<ul>
<li>메모저장</li>
<li>나만의 노래 리스트</li>
<li>즐겨찾기 <em>etc...</em>(피드백 감사)</li>
</ul>
<p>다음과 같은 일을 하기위해 과거에는 <strong>SQLite</strong> 라는 데이터 베이스를 이용하였으나
<img src="https://images.velog.io/images/yunsung_/post/22ee762a-1d2b-4e3a-b51e-f66e23061cb2/image.png" alt=""></p>
<p>다음과 같이... 사용하기 어렵다는 빨간줄이 막 그어져있다.(구글도 인정한 어려움 ㅠㅠ 🤬) 
그.래.서 나온것이 <strong>Room</strong> 친구라는것이다! </p>
<ul>
<li>구글도 SQLite 대신 Room을 사용할 것을 권장하고있다. 💁</li>
</ul>
<p>하지만 그렇다고해서 Room이 완전히 달라진것은 아니고 
SQLite 이 친구를 활용하는 개념이다 라고만 보면된다. </p>
<h3 id="2room-너-어떻게-생겼어">2.Room 너 어떻게 생겼어?</h3>
<p><img src="https://images.velog.io/images/yunsung_/post/618e61ad-ff08-4e3f-aa30-56019fdcc52d/image.png" alt=""></p>
<p>위 사진에서 </p>
<ul>
<li>Room Database</li>
<li>Data Access Objects</li>
<li>Entities </li>
</ul>
<p>이 세가지 부분이 Room의 구성요소이고 Rest of The App은 앱의 나머지 부분을 뜻합니다. 
<code>즉, (Room Database - DAO - Entities) ↔️ Rest of The App</code>
다음과 같이 세가지 요소와 서로 <code>get,set!</code> 과 같이 서로 주고받는다! 
정도로 이해하고 넘어가시면 됩니다.</p>
<p>Room 의 요소 하나하나는 조금이따가 설명해드릴 예정입니다!😎</p>
<h2 id="그래서-사용은-어떻게해야하는건데-📝">그래서 사용은 어떻게해야하는건데? 📝</h2>
<h3 id="1gradle-추가">1.gradle 추가!</h3>
<p>👉 JAVA 기준</p>
<pre><code>    implementation &#39;androidx.room:room-runtime:2.2.6&#39;
    annotationProcessor &#39;androidx.room:room-compiler:2.2.6&#39;
</code></pre><p>👉 Kotiln 기준</p>
<pre><code>// gradle맨 위쪽에 추가

         plugins {
            id &#39;kotlin-kapt&#39;
        }
     implementation &#39;androidx.room:room-runtime:2.2.6&#39;
     kapt &#39;androidx.room:room-compiler:2.2.6&#39;</code></pre><h3 id="2entity">2.Entity</h3>
<p><img src="https://images.velog.io/images/yunsung_/post/0481c641-0f2d-47b1-907d-eb19ceb4da30/image.png" alt=""></p>
<blockquote>
<p>관련이 있는 속성들이 한곳에 모여 하나의 정보단위 를 이룬것을 말합니다!</p>
</blockquote>
<p>위의 사진과 같이 Primary Key(해당 테이블의 이름)를 기준으로 
관련있는 사용자의 정보가 한곳에 모여있는것을 보시면 이해하기 쉽습니다.</p>
<pre><code>// @Entity(tableName=&quot;userProfile&quot;) 와 같이 직접 해당 테이블의 이름을 지정한다.
// 놔두면 해당클래스 이름으로 테이블이름을 지정한다.

@Entity(tableName = &quot;userinfo&quot;)
data class UserEntity (
    //기본키설정
    @PrimaryKey(autoGenerate = true)
    @ColumnInfo(name = &quot;id&quot;) val id : Int = 0,
    //필드명 즉, 열의 이름을 다르게
    @ColumnInfo(name = &quot;name&quot;) val name : String,
    @ColumnInfo(name = &quot;email&quot;) val email : String
)</code></pre><p><strong>이제 개념을 알았으니 위의 코드를 보며 공부해보자! 💁</strong></p>
<p>우선 Enitiy를 생성해야 한다.(데이터베이스 테이블을 만든다고 생각하자!)</p>
<p>data class에 @Entity *어노테이션을 붙어주고 저장하고싶은 속성의 
변수 이름과 타입을 지정해주자!</p>
<p>또한 primaryKey 라는 어노테이션도 보이는데, 이 어노테이션은 대표값 즉, 
각각의 유저가 가진 id 와 같이 중복되지 않는 값을 넣을 때 사용되는데
위의 어노테이션이 적용되면 해당 변수는 기본키가 된다.</p>
<p><code>primaryKey는 유일한 키값이기 때문에 유일한 값이어야한다.본인이 직접 지정할 수 도있지만 autoGenerate = true으로 자동 생성 할 수도있다.</code></p>
<blockquote>
<p>*어노테이션(annotation) : &#39;@&#39;가 붙은 데이터를 설명하는 데이터</p>
</blockquote>
<br>

<h3 id="3-dao">3. DAO</h3>
<blockquote>
<p>데이터에 접근할 수 있는 메서드를 정의해놓은 인터페이스이다.</p>
</blockquote>
<pre><code>@Dao
interface UserDao {

    //Insert = 데이터베이스에서 값을 꺼내거나 값을 넣을때 인자값으로 끼워넣을 수 있다.

    @Insert
    fun insertUser(user : UserEntity?)

    @Delete
    fun deleteUser(user : UserEntity)

    @Update
    fun UpdateUser(user : UserEntity?)

}</code></pre><p>즉, Entity를 어떻게 해줄건지 <strong>결정하는 설명서</strong>🧾 라고 보면될거같다.</p>
<p>Dao는 위와같이 이렇게 생성하면된다. (interface 라는점을 유의하면서보자) </p>
<p>Entity와 같이 어노테이션을 지정하지만 이번에는 @Dao 어노테이션을 지정하고 그 안에 메소드를 지정해주는데</p>
<ul>
<li>@Insert를 붙이면 테이블에 데이터 삽입</li>
<li>@Update를 붙이면 테이블의 데이터 수정</li>
<li>@Delete를 붙이면 테이블의 데이터 삭제</li>
</ul>
<p>라고 이해하면 될거같다.</p>
<p>그렇다면 위와같이 삽입/수정/삭제 이외의 다른 기능을 하는 메서드를 만들고싶다면 어떻게 해야할까??</p>
<pre><code>    @Query(&quot;SELECT * FROM userinfo ORDER BY id DESC&quot;)
    fun getAllUserInfo() : List&lt;UserEntity&gt;
    ------------------------------------------------------
      @Query(&quot;SELECT * FROM User&quot;) 
      // 테이블의 모든 값을 가져와라
    fun getAll(): List&lt;User&gt;
    ------------------------------------------------------
    @Query(&quot;DELETE FROM User WHERE name = :name&quot;) 
    // &#39;name&#39;에 해당하는 유저를 삭제해라
    fun deleteUserByName(name: String)</code></pre><p>그렇다면 위와같이 @Query 어노테이션을 통해 선언하고 그 안에 어떤동작을 할건지 SQL 문법으로 작성을 해주어야한다.</p>
<h3 id="4-room-database">4. Room DataBase</h3>
<pre><code>//entities에는 앞서 만들었던 엔티티를 넣어준다.

@Database(entities = [UserEntity::class],version = 1)
abstract class RoomAppDB : RoomDatabase()  {</code></pre><blockquote>
<p>말 그대로 데이터베이스를 생성해준다.</p>
</blockquote>
<p>이번에는 데이터베이스를 생성하고 관리하는 데이터베이스 객체 만들기 위해서 위와 같은 추상 클래스를 만들어 줘야 한다. 우선 RoomDatabase 클래스를 상속받고, @Database 어노테이션으로 <strong>데이터베이스임</strong>을 표시한다.</p>
<p>또한 <strong>version</strong>은 앱을 업데이트하다가 entity의 구조를 변경해야 하는 일이 생겼을 때 이전 구조와 현재 구조를 구분해주는 역할을 한다.</p>
<pre><code>@Database(entities = arrayOf(User::class, Student::class), version = 1)
abstract class UserDatabase: RoomDatabase() {
    abstract fun userDao(): UserDao
}</code></pre><p>만약 여러개의 Entitiy가 들어가는 경우라면 위와같이 arrayof 안에 ,로 구분해 넣어준다.</p>
<p><img src="https://images.velog.io/images/yunsung_/post/895f02fe-76e5-48b5-bc8c-b8fece869c52/image.png" alt=""></p>
<p><a href="https://developer.android.com/training/data-storage/room?hl=ko">공식문서</a>에서는 데이터베이스 객체를 인스턴스 할 때 싱글톤으로 구현하기를 권장하고 있습니다!</p>
<p><strong>Why?</strong>
일단 여러 인스턴스에 액세스를 꼭 해야 하는 일이 거의 없고, 객체 생성에 비용이 많이 들기 때문입니다.</p>
<blockquote>
<p>이 부분에 대한 이해가 잘 안 된다면 다음 목차로 넘어가도 당장 문제가 생기거나 하진 않습니다. 어디까지나 &quot;권장&quot; 하는 부분이고, 알아 두어야 할 부분이라는 걸 고려만 해두고 넘어가도 무관합니다.</p>
</blockquote>
<pre><code>@Database(entities = [User::class], version = 1)
abstract class UserDatabase: RoomDatabase() {
    abstract fun userDao(): UserDao

    companion object {
        private var instance: UserDatabase? = null

        @Synchronized
        fun getInstance(context: Context): UserDatabase? {
            if (instance == null) {
                synchronized(UserDatabase::class){
                    instance = Room.databaseBuilder(
                        context.applicationContext,
                        UserDatabase::class.java,
                        &quot;user-database&quot;
                    ).build()
                }
            }
            return instance
        }
    }
}</code></pre><p>위와 같이 싱글톤을 사용하기 위해 companion object 로 객체를 선언해서 사용해주면 됩니다. 객체를 생성할 때 databaseBuilder라는 static 메서드를 사용하는데</p>
<ul>
<li>context</li>
<li>database 클래스 </li>
<li>데이터 베이스를 저장할 때 </li>
</ul>
<p>사용할 데이터베이스의 이름을 정해서 넘겨주면 됩니다.</p>
<p><code>❗️주의</code>
<code>다른 데이터베이스랑 이름이 겹치면 꼬여버리니 주의하자!!</code></p>
<h3 id="5-대망의-데이터베이스-사용👻">5. 대망의 데이터베이스 사용👻</h3>
<pre><code>var newUser = User(&quot;김똥깨&quot;, &quot;20&quot;, &quot;010-1111-5555&quot;)

val db = UserDatabase.getInstance(applicationContext)
        db!!.userDao().insert(newUser)</code></pre><blockquote>
<p>다음 형식은 예시일뿐이다.</p>
</blockquote>
<p>위와 같이 전에 만들어놓은 데이터베이스에 insert를 해주면 값이 들어가게된다.</p>
<p><code>❗️주의!</code>
<code>Cannot access database on the main thread since it may potentially lock the UI for a long period of time 와 같은 오류가 생길것이다. 위와같은 오류는 다음과 같이 해결할 수 있는데</code></p>
<pre><code>
1. 컴퓨터! 너 실행하라구 😤

-&gt; allowMainThreadQueries()를 사용해 강제로 실행시킨다

-&gt; 하.지.만 나중에 문제가 생길 수 있다. (공부하는 입장에서는 무관)



2. 컴퓨터님.. 어.. 실행 해주세요 뀨유~ 🙄

-&gt; 비동기 실행을 하면 된다.
</code></pre><hr>
<p>출처 : <a href="https://todaycode.tistory.com/39#recentComments">https://todaycode.tistory.com/39#recentComments</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Android Bottom AppBar]]></title>
            <link>https://velog.io/@yunsung_/Android-Bottom-AppBar</link>
            <guid>https://velog.io/@yunsung_/Android-Bottom-AppBar</guid>
            <pubDate>Sat, 22 May 2021 14:23:13 GMT</pubDate>
            <description><![CDATA[<h2 id="bottom-appbar란">Bottom AppBar란?</h2>
<p><img src="https://images.velog.io/images/yunsung_/post/64dd0bc1-eb09-4f4a-badc-48742fc986dd/image.png" alt=""></p>
<p>Material 2.0에서 소개된 바텀앱바는 특정한 한두가지의 주요한 동작(액션)이 있는 앱에 사용하기 적합한 UI입니다. 사용자에게 보여주어야할 메뉴가 많은경우에는 Drawer, 4~5가지 정도의 범주로 나누어진다면 Bottom Navigation View 혹은 TabLayout + Viewpager가 적합합니다.</p>
<h2 id="사용하기-유용한-상황">사용하기 유용한 상황!</h2>
<p>ex) </p>
<ul>
<li>TO-DO리스트 </li>
<li>노트처럼 할일만들기 </li>
<li>노트생성과 같은 기능</li>
</ul>
<p>즉, 기능이 명확할때! 사용하기 좋습니다.    </p>
<h2 id="bottom-appbar의-장점과-단점">Bottom AppBar의 장점과 단점!</h2>
<p><img src="https://images.velog.io/images/yunsung_/post/3fabb549-0e19-4233-94b3-fe64b9894d63/image.png" alt=""></p>
<p>Bottom AppBar는 다음과같이 사용자가 스크롤을 해야한다고 미리 암시해놓을 수 있습니다.
동그란 Floating App Bar와 Bottom App Bar 사이에 빈 공간을통해 컨텐츠를 일부분 노출함으로써 직관적으로 스크롤을 내려야 더 많은 컨텐츠를 볼 수 있다고 알려줍니다.</p>
<p><img src="https://images.velog.io/images/yunsung_/post/496a7152-e9e9-4961-8e11-c62ca1069dfd/image.png" alt=""></p>
<p>또한 큰 장점중의 하나는 다음과 같이 스크롤 시 하단 부분을 가려 전체화면과 동일한 레이아웃효과를 제공합니다.</p>
<p><img src="https://images.velog.io/images/yunsung_/post/3677991e-ae75-4ea2-9c6e-2b4e1ac53b2d/image.png" alt=""></p>
<p>하지만 다음과같이 Tost가 Appbar를 가릴 수 도있으니 주의해서 사용해야합니다.</p>
<h2 id="bottom-appbar-만들어보기">Bottom AppBar 만들어보기!</h2>
<p><strong>기본형식</strong></p>
<pre><code>&lt;androidx.coordinatorlayout.widget.CoordinatorLayout
    xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    xmlns:app=&quot;http://schemas.android.com/apk/res-auto&quot;
    android:layout_width=&quot;match_parent&quot;
    android:layout_height=&quot;match_parent&quot;&gt;

  &lt;!-- FrameLayout 혹은 컨텐츠 영역 --&gt;

  &lt;com.google.android.material.bottomappbar.BottomAppBar
      android:id=&quot;@+id/bar&quot;
      android:layout_width=&quot;match_parent&quot;
      android:layout_height=&quot;wrap_content&quot;
      android:layout_gravity=&quot;bottom&quot;
      app:navigationIcon=&quot;@drawable/ic_menu_24&quot;/&gt;

  &lt;com.google.android.material.floatingactionbutton.FloatingActionButton
      android:id=&quot;@+id/fab&quot;
      android:layout_width=&quot;wrap_content&quot;
      android:layout_height=&quot;wrap_content&quot;
      app:layout_anchor=&quot;@id/bar&quot;/&gt;

&lt;/androidx.coordinatorlayout.widget.CoordinatorLayout&gt;</code></pre><p>다음이 Bottom AppBar의 기본 형식입니다. BottomAppBar의 주의할점은 CoordinatorLayout과 함께 사용해야하며 안드로이드 스튜디오 3.2 이하버전에서는 레이아웃이 제대로 표현되지 않을 수 있습니다.</p>
<p><strong>AppBar의 속성</strong></p>
<p>FAB Alignment(app:fabAlignmentMode)
앱바의 정렬 위치를 설정할 수 있습니다.
<img src="https://images.velog.io/images/yunsung_/post/e46141ec-5740-46d2-a71b-2b89a2965f63/image.png" alt="">
fabAlignment: CENTER
<img src="https://images.velog.io/images/yunsung_/post/fb6d7f86-ebc2-499f-a811-05686def4175/image.png" alt="">
fabAlignment: End</p>
<br>
다음과 같은 코드로 프레그먼트가 전환될때 애니메이션을 줄 수도있습니다.

<pre><code>app_bar.fabAlignmentMode = BottomAppBar.FAB_ALIGNMENT_MODE_END</code></pre><p><img src="https://images.velog.io/images/yunsung_/post/18ae9822-c46d-4217-adb9-08468148686c/image.png" alt=""></p>
<p><strong>FAB Attached</strong></p>
<p>바텀앱바와 플로팅버튼이 떨어져있을지 붙어있을지 설정할 수 있습니다. false로 한다면 아래와같은 형태로 볼 수 있습니다.</p>
<p><img src="https://images.velog.io/images/yunsung_/post/cf3fc0c4-7584-4b73-a29e-a04e4bfc31dc/image.png" alt=""></p>
<p><strong>fabCradleRoundedCornerRadius</strong>
플로팅버튼과 AppBar사이의 간격을 조절할 수 있습니다.</p>
<p><img src="https://images.velog.io/images/yunsung_/post/0c0ccb22-23e8-4b18-89a3-61a71aa74e8d/image.png" alt=""></p>
<p><strong>navigation</strong></p>
<p><img src="https://images.velog.io/images/yunsung_/post/7b8c5219-252e-40b1-ac27-f2c7647eb06d/image.png" alt=""></p>
<p>왼쪽의 네비게이션 아이콘과 오른쪽의 메뉴아이콘입니다. 주로 왼쪽은 DrawrLayout처럼 여러 아이콘을 보여줄때 사용합니다.
주로 BottomSheetDialog를 통해 구현합니다.</p>
<pre><code>bottomAppBar.setNavigationOnClickListener</code></pre><p> 반면 오른쪽메뉴는 즐겨찾기, 검색과같이 사용자의 사용빈도가 조금 더 많은 메뉴들을 구현할때 사용합니다. 클릭될 경우 아래와 아래와 같은 리스너를 사용해 구현합니다.</p>
<pre><code> bottom_app_bar.replaceMenu(R.menu.menu_bottom_nav)
bottom_app_bar.setOnMenuItemClickListener({
    //구현부분
})</code></pre><hr>
<p>[출처] : <a href="https://medium.com/harrythegreat/android-bottom-appbar-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0-9071ec428138">https://medium.com/harrythegreat/android-bottom-appbar-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0-9071ec428138</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Companion Object]]></title>
            <link>https://velog.io/@yunsung_/Companion-Object</link>
            <guid>https://velog.io/@yunsung_/Companion-Object</guid>
            <pubDate>Fri, 14 May 2021 17:02:53 GMT</pubDate>
            <description><![CDATA[<h2 id="thiking🤨">Thiking🤨</h2>
<hr>
<p>안드로이드 Room DB를 공부하던도중 Companion Object라는 키워드를 발견했습니다. 여기서 궁금증이 생겼습니다. 과연 Companion Object가 무엇일까요? 단순히 자바(JAVA)의 static 키워드를 대체하기위해 만들어진 키워드일까요? 이 질문이 우리가 이번에 배우는데에 큰 도움을 줄것입니다.</p>
<h2 id="lets-go">Let&#39;s Go!</h2>
<p>자바의 static 키워드는 클래스 멤버(member)임을 지정하기 위해 사용합니다. 
<strong>static이 붙은 변수와 메소드를 각각 클래스 변수, 클래스 메소드</strong>라 부릅니다. 반면,
<strong>static이 붙지 않은 클래스 내의 변수와 메소드는 각각 인스턴스 변수, 인스턴스 메소드</strong>라 합니다. static이 붙은 멤버는 클래스가 메모리에 적재될 때 자동으로 함께 생성되므로 인스턴스 생성 없이도 클래스명 다음에 점(.)을 쓰면 바로 참조할 수 있습니다.</p>
<pre><code>public final class JAVAClass{
  static public final String TEST = &quot;This is JAVA Test&quot;;  //클래스 변수
  static public method(int i):int{     //클래스 메소드
    return i + 10
  }
}
System.out.println(MyClass.TEST);    //test
System.out.println(MyClass.method(1));   //11</code></pre><p>하지만 JAVA에 있는 static이 코틀린에는 없습니다. 대신 Kotiln은 Companion Object라는 키워드와 함께 블록({})안에 멤버를 구성합니다.</p>
<pre><code>
class KotilnClass{
    companion object{
        val TEST = &quot;This is Kotiln Test&quot;
        fun method(i:Int) = i + 10
    }
}
fun main(args: Array&lt;String&gt;){
    println(MyClass.TEST);    //test
    println(MyClass.method(1));   //11
}</code></pre><p>이 시점에서보면 static과 다를 바가 없습니다. 하지만!! companion object와 static의 차이점은 분명히 있고 static의 한계에 따라 companion object를 쓰는이유를 이제부터 저와 알아봅시다.</p>
<h2 id="kotiln-class-키워드-기초">Kotiln Class 키워드 기초</h2>
<p>우선 코틀린의 class 와 Object를 이해해 봅시다.</p>
<p><strong>class</strong></p>
<pre><code>class WhatThisFood(private val name:String){
    fun FavoriteFood() = &quot;내가 좋아하는 음식은 ${name}입니다.&quot;
}
fun main(args: Array&lt;String&gt;){
    val food1 = WhatThisFood(&quot;마라탕&quot;)
    val food2 = WhatThisFood(&quot;김치찌게&quot;)
    println(food1.WhatThisFood()) // 내가 좋아하는 음식은 마라탕입니다.
    println(food2.WhatThisFood()) // 내가 좋아하는 음식은 김치찌게입니다.
}</code></pre><p>위쪽은 Kotiln Class를 정의하는 방법입니다. 이경우 클래스 WhatThisFoodf를 정의하면서 val name:String값을 생성자의 인자로 받습니다. 자바를 주로 사용하셨던분이라면 이해가 안되실수도 있습니다. 이제 이 시점에서 자바코드로 바꾸어 보겠습니다.</p>
<pre><code>class WhatThisFood{
    private val name:String
    constructor(name:String){
        this.name = name
    }
    fun FavoriteFood() = &quot;내가 좋아하는 음식은 ${name}입니다.&quot;
}
</code></pre><p>위처럼 클래스 생성자는 constructor 키워드를 써서 정의합니다. 그리고 생성자에서 받은인자 name:String인 값을 속성인 this:name을 통해 private val name:String에 할당하고있습니다.</p>
<p>여기에서의 문제점은 <strong>중복되는 부분이 너무 많다</strong>는것입니다.
이러한 문제점을 Kotiln의 Class를 정의하면서 인자값을 받아오는부분으로 해결한것입니다!</p>
<p>즉, val m1 = WhatThisFood(“영수”) 꼴로 클래스에서 객체를 생성합니다.
코틀린에서는 new 키워드 없이 클래스 명 뒤에 괄호()를 붙인다는 점!! (마치 함수호출😁)</p>
<p><strong>Object</strong></p>
<p>코틀린에는 자바에는 없는 &quot;특별한&quot; 싱글턴(Singletion : 인수가 하나만 있는 클래스)가 있습니다. 다음과같이 class 대신 object라는 키워드를 사용합니다. 한번만 객체가 생성되게 하므로 메모리 낭비를 방지할 수 있고, 전역변수이므로 공유도 용이합니다.</p>
<pre><code>object MySingleton{
    val prop = &quot;나는 MySingleton의 속성이다.&quot;
    fun method() = &quot;나는 MySingleton의 메소드다.&quot;
}
fun main(args: Array&lt;String&gt;){
    println(MySingleton.prop);    //나는 MySingleton의 속성이다.
    println(MySingleton.method());   //나는 MySingleton의 메소드다.
}</code></pre><p>싱글톤이기 때문에 시스템 전체에서 쓸 기능(메소드로 정의)을 수행하는 데는 큰 도움이 될 수 있지만, 전역 상태를 유지하는 데 쓰면 스레드 경합 등으로 위험할 수 있으니 주의해서 사용해야 합니다.</p>
<p>이제 우리는 class 와 object에 대해 간결히 배워보았습니다. 이제부터는 이 지식들을 가지고 companion object를 다뤄봅시다!</p>
<h2 id="companion-object는-static이-아니다">Companion object는 static이 아니다!</h2>
<p>사실 Companiond Object는 static과 동일한것이 아니며, 동일하게 보이는것 뿐입니다.
이게 무슨말일까요? 밑의 예제를 살펴봅시다.</p>
<pre><code>class MyClass2{
    companion object{
        val prop = &quot;나는 Companion object의 속성이다.&quot;
        fun method() = &quot;나는 Companion object의 메소드다.&quot;
    }
}
fun main(args: Array&lt;String&gt;) {
    //사실은 MyClass2.맴버는 MyClass2.Companion.맴버의 축약표현이다.
    println(MyClass2.Companion.prop)
    //println(MyClass2.prop)와 같다.

    println(MyClass2.Companion.method())
}</code></pre><p>MyClass2 클래스에 companion object를 만들어 2개의 멤버를 정의했습니다. 이를 사용하는 main() 함수를 보면 이 멤버에 접근하기 위해 클래스명.Companion형태로 쓴 것을 확인할 수 있습니다. </p>
<p>즉, companion object{}는
<strong>MyClass2클래스가 메모리에 적재되면서 함께 생성되는 동반(companion)되는 객체</strong>입니다. 여기서 우리는 변수를 사용하기위해 같이 사용됨을 유추해 볼 수있습니다.</p>
<p>따라서 우리가 알 수 있는사실은 위의 MyClass2.prop 는 MyClass2.Companio.prop의 축약형이라는것을 알 수 있습니다. 우리가 지금까지 생각한 생각은 언어적으로 지원하는 축약 표현 때문에 companion object가 static으로 착각이 드는 것때문이라는것을 알 수 있습니다.</p>
<h2 id="companion-object는-객체입니다">Companion object는 객체입니다.</h2>
<pre><code>class MyClass2{
    companion object{
        val prop = &quot;나는 Companion object의 속성이다.&quot;
        fun method() = &quot;나는 Companion object의 메소드다.&quot;
    }
}
fun main(args: Array&lt;String&gt;) {
    println(MyClass2.Companion.prop)
    println(MyClass2.Companion.method())
    //이렇게 변수에 할당하는 것은 자바의 클래스에서 static 키워드로 정의된 멤버로는 불가능한 방법입니다.
    val comp1 = MyClass2.Companion  //--(1)
    println(comp1.prop)
    println(comp1.method())
    val comp2 = MyClass2  //--(2)
    println(comp2.prop)
    println(comp2.method())
}</code></pre><p>위와 같은 코드에서 우리는 Companion Object가 객체임을 알 수 있습니다
(1)번 주석과 같이 companion Object를 변수에 할당하는것을 볼 수있고,
(2)번 주석과 같이 .Companion을 빼고 클래스안에 들어가있는 companion Object 를 변수에 할당을 할 수있는것도 볼 수 있습니다.</p>
<p>즉, static 과는 다르게 companion object는 독립된 객체임을 알 수있습니다.</p>
<h2 id="companion-object에-이름을-지을-수-있습니다">Companion object에 이름을 지을 수 있습니다.</h2>
<p>companion object의 기본 이름은 Companion이지만 이름을 변경 할 수도 있습니다.</p>
<pre><code>class MyClass3{
    companion object MyCompanion{  // -- (1)
        val prop = &quot;나는 Companion object의 속성이다.&quot;
        fun method() = &quot;나는 Companion object의 메소드다.&quot;
    }
}
fun main(args: Array&lt;String&gt;) {
    println(MyClass3.MyCompanion.prop) // -- (2)
    println(MyClass3.MyCompanion.method())
    val comp1 = MyClass3.MyCompanion // -- (3)
    println(comp1.prop)
    println(comp1.method())
    val comp2 = MyClass3 // -- (4)
    println(comp2.prop)
    println(comp2.method())
    val comp3 = MyClass3.Companion // -- (5) 이름이 바뀌었으므로 에러발생!!!
    println(comp3.prop)
    println(comp3.method())
}</code></pre><p>위의 그림을 보면 주석(1)번에서 companion object에 이름을 주자, 주석(2),(3)번이 변경된 이름그대로 사용 할 수있음을 알 수 있습니다.
그러나 여전히 주석(4)번처럼 Companion키워드를 생략 할 수도있습니다.</p>
<h2 id="클래스내-companion-object는-딱-하나만-쓸-수-있습니다">클래스내 Companion object는 딱 하나만 쓸 수 있습니다.</h2>
<p>클래스내에서 Companion Object는 딱 하나만 쓸 수있습니다. 지금까지 공부한것을 생각해 보았을때 당연한 결과일지도 모릅니다. 왜냐하면 코틀린은 클래스명만으로도 참조가 가능하므로 한 클래스안에 두개의 Companion Object가 들어가있으면 무엇을 가져와야할지 결정을 못하기 때문입니다.</p>
<pre><code>class MyClass5{
    companion object{
        val prop1 = &quot;나는 Companion object의 속성이다.&quot;
        fun method1() = &quot;나는 Companion object의 메소드다.&quot;
    }
    companion object{ // -- 에러발생!! Only one companion object is allowed per class
        val prop2 = &quot;나는 Companion object의 속성이다.&quot;
        fun method2() = &quot;나는 Companion object의 메소드다.&quot;
    }
}</code></pre><p>위처럼 만들면 Only one companion object is allowed per class 에러가 발생합니다.
그렇다고 밑의 코드처럼 Companion object에 이름을 서로 다른이름으로 할당해주어도 오류가 발생합니다.</p>
<pre><code>class MyClass5{
    companion object MyCompanion1{
        val prop1 = &quot;나는 Companion object의 속성이다.&quot;
        fun method1() = &quot;나는 Companion object의 메소드다.&quot;
    }
    companion object MyCompanion2{ // --  에러발생!! Only one companion object is allowed per class
        val prop2 = &quot;나는 Companion object의 속성이다.&quot;
        fun method2() = &quot;나는 Companion object의 메소드다.&quot;
    }
}</code></pre><h2 id="상속-관계에서-companion-object-멤버는-같은-이름일-경우-가려집니다섀도잉-shadowing">상속 관계에서 Companion object 멤버는 같은 이름일 경우 가려집니다(섀도잉, shadowing).</h2>
<p>부모 클래스를 상속한 자식 클래스에 모두 companion object를 만들고 같은 이름의 멤버를 정의했다고 가정합니다. 이때, 자식 클래스에서 이 멤버를 참조하면 부모의 멤버는 가려지고 자식 자신의 멤버만 참조할 수 있습니다. 이해하기 쉽도록 코드로 보겠습니다.</p>
<pre><code>open class Parent{
    companion object{
        val parentProp = &quot;나는 부모값&quot;
    }
    fun method0() = parentProp
}
class Child:Parent(){
    companion object{
        val childProp = &quot;나는 자식값&quot;
    }
    fun method1() = childProp
    fun method2() = parentProp
}
fun main(args: Array&lt;String&gt;) {
    val child = Child()
    println(child.method0()) //나는 부모값
    println(child.method1()) //나는 자식값
    println(child.method2()) //나는 부모값
}</code></pre><p>위 코드처럼 부모/자식의 companion object의 멤버가 다른 이름이라면 자식이 부모의 companion object 멤버를 직접 참조할 수 있습니다. 하지만 같은 이름이면 어떻게 될까요? 다음 코드를 봅시다.</p>
<pre><code>open class Parent{
    companion object{
        val prop = &quot;나는 부모&quot;
    }
    fun method0() = prop //Companion.prop과 동일
}
class Child:Parent(){
    companion object{
        val prop = &quot;나는 자식&quot;
    }
    fun method1() = prop //Companion.prop 과 동일
}
fun main(args: Array&lt;String&gt;) {
    println(Parent().method0()) //나는 부모
    println(Child().method0()) //나는 부모
    println(Child().method1()) //나는 자식
    println(Parent.prop) //나는 부모
    println(Child.prop) //나는 자식
    println(Parent.Companion.prop) //나는 부모
    println(Child.Companion.prop) //나는 자식
}</code></pre><p>위에서  companion object가 같은 이름을 사용했을때의 경우를 보여줍니다.
같은이름을 사용하였을경우 자식클래스의 companion object 속성인 prop이 부모에서 정의 되어 있지만 가려져서 무시되는 것을 볼 수 있습니다.</p>
<p>즉, method0() 메소드는 Parent클래스 것이기때문에 그래서 부모의 companion object의 prop값인 “나는 부모”가 출력됩니다.</p>
<p>여기서 코드를 살짝 바꾸어보겠습니다. 자식클래스의 companion object에 이름을 부여합니다.</p>
<pre><code>open class Parent{
    companion object{
        val prop = &quot;나는 부모&quot;
    }
    fun method0() = prop
}
class Child:Parent(){
    companion object ChildCompanion{ // -- (1) ChildCompanion로 이름을 부여했어요.
        val prop = &quot;나는 자식&quot;
    }
    fun method1() = prop
    fun method2() = ChildCompanion.prop
    fun method3() = Companion.prop
}
fun main(args: Array&lt;String&gt;) {
    val child = Child()
    println(child.method0()) //나는 부모
    println(child.method1()) //나는 자식
    println(child.method2()) //나는 자식
    println(child.method3()) // -- (2)
}</code></pre><p>위 코드에서 주석 (1)에 자식 클래스의 companion object에 ChildCompanion로 이름을 부여했습니다. 그리고 자식 클래스에 3개의 메소드를 정의했습니다. </p>
<p>child.method0()은 <strong>부모의 method</strong>이므로 어렵지 않게 “나는 부모”가 출력된 것을 예상할 수 있습니다. 그리고 child.method1()과 child.method2()도 역시 자식의 companion object의 속성을 가리킨다는 것을 알 수 있습니다.</p>
<p>주석(2)는 어떤 결과가 나올까요? 답부터 말씀드리면 “나는 부모”입니다. 자식의 companion object를 뛰어넘어서 부모의 companion object에 직접 접근이 가능하게 되었습니다.</p>
<p>왜냐하면 자식은 ChildCompanion로 이름을 바꿨으니 부모의 Comaniond을가져와 여기서 Companion은 부모가 됩니다.</p>
<p>우리가 여기서 알 수 있는점은  부모/자식의 companion object에 정의된 멤버는 자식 입장에서 접근할 수 있지만, 이름이 겹친다면 <strong>섀도잉(shadowing)</strong> 이 되어 감춰진다는 점을 알 수 있었습니다.</p>
<hr>
<p>출처 :<a href="https://www.bsidesoft.com/8187">https://www.bsidesoft.com/8187</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[ListView와 RecyclerView]]></title>
            <link>https://velog.io/@yunsung_/ListView%EC%99%80-RecyclerView</link>
            <guid>https://velog.io/@yunsung_/ListView%EC%99%80-RecyclerView</guid>
            <pubDate>Wed, 07 Apr 2021 11:45:47 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>ListView대신 RecyclerView가 나온 배경</p>
</blockquote>
<p><code>ListView</code>에서는 모든 데이터에대한 View를 만들고, View가 사라졌다가
나타날때마다 리소스를 불러와야한다. </p>
<p>즉 이말은 화면을 아래로 스크롤했다가 다시 위로 올릴때마다 리소스를 다시 불러오게 된다는소리다. </p>
<p>이러한 방법은 많은 메모리와 저장공간을 사용하기 때문에 대용량의 정보(데이터)를 불러오게된다면 앱이 느려지거나 충돌할 가능성이 있다.</p>
<p><img src="https://images.velog.io/images/yunsung_/post/58622294-cbef-4f61-ac82-f896bd8d5fdc/image.png" alt=""></p>
<p>이러한 이유때문에 <code>RecyclerView</code>가 ListView의 단점을 보안하기위해 나온것이다. </p>
<p><code>ViewHolder</code>를 필수적으로 사용해야하고, <code>LayoutManager</code>를 설정하는등 ListView보다 더 복잡한 구조긴하지만 앱에서 <code>메모리를 효율적으로 사용</code>할 수 있게 해주니 복잡한 구조말고는 장점이 두드러지는것이다.</p>
<blockquote>
<p>사용하기위해 준비해야할것</p>
</blockquote>
<pre><code>1.Gradle에서 Implement 추가
2.데이터 클래스 정의
3.레이아웃에 RecyclerView 추가
4.item 생성
5.어댑터 생성
6.어댑터 설정</code></pre><p>다음과 같은 리스트를 다뤄보도록 하겠다.
<img src="https://images.velog.io/images/yunsung_/post/7cb40672-697b-4aed-bef9-c0994e1b2121/image.png" alt=""></p>
<h3 id="1-gradle에서-implementation-추가">1. Gradle에서 Implementation 추가</h3>
<blockquote>
<p>RecyclerView는 기본 API에 제공되어 있지 않기 때문에, Support Library 추가를 해주어야 사용가능</p>
</blockquote>
<p><img src="https://images.velog.io/images/yunsung_/post/8abca15d-a86b-44be-80f4-1d4d2c86f99e/image.png" alt=""></p>
<p>Gradle Scrpits - build.gradle (Module: app)경로로 파일을 열어서 dependencies 안에 implementation을 추가해주어야한다.</p>
<pre><code>implementation &#39;com.android.support:recyclerview-v7:26.1.0&#39;</code></pre><p>혹은</p>
<pre><code>implementation &quot;androidx.recyclerview:recyclerview:1.1.0&quot;</code></pre><p>을 입력해준 다음 gradle 파일을 수정하고 나면 우측 상단의 Sync Now 를 눌러서 설정을 업데이트 한다.</p>
<h3 id="2-데이터-클래스-정의">2. 데이터 클래스 정의</h3>
<blockquote>
<p>데이터를 저장하는 공간!</p>
</blockquote>
<p>여기서는 개의 목록을 나타내는 RecyclerView를 만들기때문에 새로운 클래스인 DogClass를 만들었다. 데이터 변수는 breed,gender,age,photo로 구성하였다.</p>
<ul>
<li>여기서 photo는 <code>drawable</code>에 들어갈 이미지 파일의 이름</li>
</ul>
<pre><code>
class Dog (val breed: String, val gender: String, val age: String, val photo: String)</code></pre><p>그리고 변수를 초기화한후 개의 목록을 가지고있을 ArrayList를 추가로 써준다.</p>
<pre><code>/* MainActivity.kt* /

var dogList = arrayListOf&lt;Dog&gt;() // 우선 빈 dogList라는 리스트 생성</code></pre><h3 id="3레이아웃에-recyclerview-추가">3.레이아웃에 RecyclerView 추가</h3>
<p>전에 추가한 Gradle에 대해서 추가한 코드가 이번에는 빛을 발할시간이다! <code>&lt;android.support.v7.widget.RecyclerView&gt;</code> 경로를 통해 RecyclerView를 레이아웃에 추가하였다!</p>
<pre><code>// XML Layout코드
&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;android.support.constraint.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;
    tools:context=&quot;.MainActivity&quot;&gt;

    &lt;android.support.v7.widget.RecyclerView
        android:layout_width=&quot;match_parent&quot;
        android:layout_height=&quot;match_parent&quot;
        android:id=&quot;@+id/mRecyclerView&quot;
        android:layout_marginStart=&quot;8dp&quot;
        android:layout_marginEnd=&quot;8dp&quot;
        android:layout_marginTop=&quot;8dp&quot;
        android:layout_marginBottom=&quot;8dp&quot;&gt;
    &lt;/android.support.v7.widget.RecyclerView&gt;

&lt;/android.support.constraint.ConstraintLayout&gt;</code></pre><h3 id="4-item-생성">4. Item 생성</h3>
<p>이번엔 RecyclerView의 항목 하나하나를 담당할 <code>item</code>뷰를 만든다. 여기서는 xml Layout파일을 왼쪽에 사진, 상단에 종류, 하단에 나이와 성별이 오도록 했다.</p>
<pre><code>&lt;!--main_rv_item.xml--&gt;
&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;android.support.constraint.ConstraintLayout xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    android:layout_width=&quot;match_parent&quot;
    android:layout_height=&quot;60dp&quot;
    xmlns:app=&quot;http://schemas.android.com/apk/res-auto&quot;
    xmlns:tools=&quot;http://schemas.android.com/tools&quot;
    android:background=&quot;@drawable/item_border&quot;
    android:layout_marginTop=&quot;2dp&quot;
    android:layout_marginBottom=&quot;2dp&quot;
    android:layout_marginStart=&quot;4dp&quot;
    android:layout_marginEnd=&quot;4dp&quot;&gt;

    &lt;ImageView
        android:id=&quot;@+id/dogPhotoImg&quot;
        android:layout_width=&quot;54dp&quot;
        android:layout_height=&quot;54dp&quot;
        android:layout_marginBottom=&quot;4dp&quot;
        android:layout_marginStart=&quot;8dp&quot;
        android:layout_marginTop=&quot;4dp&quot;
        app:layout_constraintBottom_toBottomOf=&quot;parent&quot;
        app:layout_constraintStart_toStartOf=&quot;parent&quot;
        app:layout_constraintTop_toTopOf=&quot;parent&quot;
        app:srcCompat=&quot;@mipmap/ic_launcher_round&quot; /&gt;

    &lt;TextView
        android:id=&quot;@+id/dogBreedTv&quot;
        android:layout_width=&quot;wrap_content&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:layout_marginStart=&quot;16dp&quot;
        android:textSize=&quot;20sp&quot;
        android:textStyle=&quot;bold&quot;
        app:layout_constraintStart_toEndOf=&quot;@+id/dogPhotoImg&quot;
        app:layout_constraintTop_toTopOf=&quot;@+id/dogPhotoImg&quot;
        tools:text=&quot;Breed&quot;/&gt;

    &lt;TextView
        android:id=&quot;@+id/dogAgeTv&quot;
        android:layout_width=&quot;wrap_content&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:textSize=&quot;16sp&quot;
        app:layout_constraintBottom_toBottomOf=&quot;@+id/dogPhotoImg&quot;
        app:layout_constraintStart_toStartOf=&quot;@+id/dogBreedTv&quot;
        tools:text=&quot;Age&quot; /&gt;

    &lt;TextView
        android:id=&quot;@+id/dogGenderTv&quot;
        android:layout_width=&quot;wrap_content&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:layout_marginStart=&quot;16dp&quot;
        android:textSize=&quot;16sp&quot;
        app:layout_constraintBottom_toBottomOf=&quot;@+id/dogAgeTv&quot;
        app:layout_constraintStart_toEndOf=&quot;@+id/dogAgeTv&quot;
        app:layout_constraintTop_toTopOf=&quot;@+id/dogAgeTv&quot;
        tools:text=&quot;Gender&quot; /&gt;

&lt;/android.support.constraint.ConstraintLayout&gt;</code></pre><p>또한 다음 테두리를 가진 레이아웃을 만들기위해 drawable을 만들고,<code>android:background=&quot;@drawable/item_border&quot;</code>옵션을 주었다.</p>
<p><img src="https://images.velog.io/images/yunsung_/post/f83b0649-d6fe-434e-a66e-003c367e8feb/image.png" alt=""></p>
<h3 id="5-어댑터-생성">5. 어댑터 생성</h3>
<p>RecyclerView와 그곳에 들어갈 각각의 item, 연동할 데이터까지 설정을 마쳤다면 <code>Adapter</code>를 만들어야한다! 다음 사진과같이 <code>사진,종류,나이,성별등 어느 요소를 View에 넣을 것인지 연결해주는것</code>이 Adapter의 역할이다! (이제부터 신경쓸것이 많아지기 시작한다)</p>
<p><img src="https://images.velog.io/images/yunsung_/post/3e583bd5-28ed-4876-9a5c-78d02261ed5b/image.png" alt=""></p>
<p>우선, <code>Context</code> 와 <code>ArrayList&lt;Class&gt;</code>를 필요로 한다.</p>
<pre><code>class MainRvAdapter(val context: Context, val dogList, ArrayList&lt;Dog&gt;) :
RecyclerView.Adapter&lt;&gt;() {

    }</code></pre><p>RecyclerView.Adapter 에서는 <code>ViewHolder</code>라는것이 필요한데, 아직 만들어지지않았다. 따라서 <code>RecyclerView.Adapter&lt;&gt;</code> 부분에 오류가 날것이다. 당황하지말자!! 여기서 여기에 필요한 Holder클래스를 추가해주면 된다.</p>
<pre><code>class MainRvAdapter(val context: Context, val dogList, ArrayList&lt;Dog&gt;):
RecyclerView.Adapter&lt;&gt;() {

    inner class Holder(itemView: View?) : RecyclerView.ViewHolder(itemView) {
        val dogPhoto = itemView?.findViewById&lt;ImageView&gt;(R.id.dogPhotoImg)
        val dogBreed = itemView?.findViewById&lt;TextView&gt;(R.id.dogBreedTv)
        val dogAge = itemView?.findViewById&lt;TextView&gt;(R.id.dogAgeTv)
        val dogGender = itemView?.findViewById&lt;TextView&gt;(R.id.dogGenderTv)

        fun bind (dog: Dog, context: Context) {
          /* dogPhoto의 setImageResource에 들어갈 이미지의 id를 파일명(String)으로 찾고,
          이미지를 불러오지 못했을경우 안드로이드 기본 아이콘을 표시한다.*/
            if (dog.photo != &quot;&quot;) {
                val resourceId = context.resources.getIdentifier(dog.photo, &quot;drawable&quot;, context.packageName)
                dogPhoto?.setImageResource(resourceId)
            } else {
                dogPhoto?.setImageResource(R.mipmap.ic_launcher)
            }
    /* 나머지 TextView와 String 데이터를 연결한다! 가져온 내용을 나타내주어야 하기때문 */
            dogBreed?.text = dog.breed
            dogAge?.text = dog.age
            dogGender?.text = dog.gender
        }
    }
}</code></pre><p>Holder의 상단에서는 각 View의 변수이름을 정해 변수를 가져오고, <code>findViewById</code>를 통해 ImageView인지 TextView인지 등등 종류를 구별하고 id를 통해 layout과 연결된다. <code>bind</code>함수는 ViewHolder와 클래스의 각 변수를 연결하는역할을 한다. 쉽게 말해 값을 itemLayout에서 가져오고 값을 대입해주는 역할을 해주는것이다.</p>
<p>그리고 상단 RecyclerView.Adapter&lt;&gt;의 괄호에 지금 만든 Holder를 넣는다. inner Class로 만들었기 때문에 MainRvAdapter.Holder를 입력한다.</p>
<pre><code>class MainRvAdapter(val context: Context, val dogList, ArrayList&lt;Dog&gt;):
RecyclerView.Adapter&lt;&gt;() //&lt;- 여기부분!</code></pre><p>또한 메인 클래스에 들어가보게되면 오류가 발생되어있는데, 필수로 사용해야 하는 함수를 Override하지 않았기 때문이다. <code>Alt + Enter</code>를 눌러 목록에 있는 세 개의 함수를 모두 Override 한다.</p>
<blockquote>
<p><strong>뷰홀더 설명</strong></p>
</blockquote>
<p>onCreateViewHolder : 화면을 최초 로딩하여 만들어진 View가 없는 경우, xml파일을 inflate하여 ViewHolder를 생성한다.</p>
<p>getItemCount : RecyclerView로 만들어지는 item의 총 개수를 반환한다.</p>
<p>onBindViewHolder : 위의 onCreateViewHolder에서 만든 view와 실제 입력되는 각각의 데이터를 연결한다.</p>
<pre><code>override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): Holder {
    val view = LayoutInflater.from(context).inflate(R.layout.main_rv_item, parent, false)
    return Holder(view)
}

override fun getItemCount(): Int {
    return dogList.size
}

override fun onBindViewHolder(holder: Holder?, position: Int) {
    holder?.bind(dogList[position], context)
}</code></pre><blockquote>
<p>모든함수 + 뷰홀더 최종코드</p>
</blockquote>
<pre><code>class MainRvAdapter(val context: Context, val dogList: ArrayList&lt;Dog&gt;) :
RecyclerView.Adapter&lt;MainRvAdapter.Holder&gt;() {
    override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): Holder {
        val view = LayoutInflater.from(context).inflate(R.layout.main_rv_item, parent, false)
        return Holder(view)
    }

    override fun getItemCount(): Int {
        return dogList.size
    }

    override fun onBindViewHolder(holder: Holder?, position: Int) {
        holder?.bind(dogList[position], context)
    }

    inner class Holder(itemView: View?) : RecyclerView.ViewHolder(itemView) {
        val dogPhoto = itemView?.findViewById&lt;ImageView&gt;(R.id.dogPhotoImg)
        val dogBreed = itemView?.findViewById&lt;TextView&gt;(R.id.dogBreedTv)
        val dogAge = itemView?.findViewById&lt;TextView&gt;(R.id.dogAgeTv)
        val dogGender = itemView?.findViewById&lt;TextView&gt;(R.id.dogGenderTv)

        fun bind (dog: Dog, context: Context) {
            if (dog.photo != &quot;&quot;) {
                val resourceId = context.resources.getIdentifier(dog.photo, &quot;drawable&quot;, context.packageName)
                dogPhoto?.setImageResource(resourceId)
            } else {
                dogPhoto?.setImageResource(R.mipmap.ic_launcher)
            }
            dogBreed?.text = dog.breed
            dogAge?.text = dog.age
            dogGender?.text = dog.gender
        }
    }
}</code></pre><h3 id="6어댑터-설정">6.어댑터 설정</h3>
<p><strong>*LayoutManager</strong> : RecyclerView의 각 item들을 배치하고, item이 더이상 보이지 않을 때 재사용할 것인지 결정하는 역할을 한다.</p>
<p>ListView Adapter와는 다르게, RecyclerView Adapter에서는 <code>*레이아웃 매니저 (LayoutManager)</code> 를 설정해주어야 한다.</p>
<blockquote>
<p>레이아웃 매니저를 써야하는 이유?</p>
</blockquote>
<p>LayoutManager 사용을 통해 불필요한 findViewById를 수행하지 않아도 되고, 앱 성능을 향상시킬 수 있다. 또한 RecyclerView를 불러올 액티비티에 LayoutManager를 추가한다.</p>
<p>마지막으로 recyclerView에 <code>setHasFixedSize</code> 옵션에 true 값을 준다.</p>
<blockquote>
<p>true 옵션을 주는이유!</p>
</blockquote>
<p> item이 추가되거나 삭제될 때 RecyclerView의 크기가 변경될 수도 있고, 그렇게 되면 계층 구조의 다른 View 크기가 변경될 가능성이 있기 때문</p>
<pre><code>       //[기본 3개의 Manager중 하나]
        //-LinearLayoutManager
        //-GridLayoutManager
        //-StaggeredGridLayoutManager

         val lm = LinearLayoutManager(this)
        mRecyclerView.layoutManager = lm
        mRecyclerView.setHasFixedSize(true)</code></pre><p> 하지만 여기서 마지막이 아니다! itemLayout에 넘겨줄 Arraylist가 비어있기 때문이다! 마지막으로 값이 잘들어가는지 Arraylist에 값을 넣어보자! 
 (Edit Text와 Button으로 값을 입력해주는것도 해줄 수 있지만 우리는 여기까지만하자)</p>
<pre><code> var dogList = arrayListOf&lt;Dog&gt;(
    Dog(&quot;Chow Chow&quot;, &quot;Male&quot;, &quot;4&quot;, &quot;dog00&quot;),
    Dog(&quot;Breed Pomeranian&quot;, &quot;Female&quot;, &quot;1&quot;, &quot;dog01&quot;),
    Dog(&quot;Golden Retriver&quot;, &quot;Female&quot;, &quot;3&quot;, &quot;dog02&quot;),
    Dog(&quot;Yorkshire Terrier&quot;, &quot;Male&quot;, &quot;5&quot;, &quot;dog03&quot;),
    Dog(&quot;Pug&quot;, &quot;Male&quot;, &quot;4&quot;, &quot;dog04&quot;),
    Dog(&quot;Alaskan Malamute&quot;, &quot;Male&quot;, &quot;7&quot;, &quot;dog05&quot;),
    Dog(&quot;Shih Tzu&quot;, &quot;Female&quot;, &quot;5&quot;, &quot;dog06&quot;)
)</code></pre><hr>
<h3 id="source">source.</h3>
<ul>
<li><a href="https://blog.yena.io/studynote/2017/12/06/Android-Kotlin-RecyclerView1.html">https://blog.yena.io/studynote/2017/12/06/Android-Kotlin-RecyclerView1.html</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Kotlin]ConstraintLayout]]></title>
            <link>https://velog.io/@yunsung_/KotlinConstraintLayout</link>
            <guid>https://velog.io/@yunsung_/KotlinConstraintLayout</guid>
            <pubDate>Sun, 04 Apr 2021 07:19:20 GMT</pubDate>
            <description><![CDATA[<h3 id="constraintlayout의-이해">ConstraintLayout의 이해</h3>
<p>우선, 설명하기전에 이 단어에 대해서 알아볼 필요가 있습니다!</p>
<pre><code>&lt;Button android:id=&quot;@+id/button1&quot; ...
         app:layout_constraintRight_toRightOf=&quot;parent&quot; /&gt;</code></pre><p><code>app:layout_constraintRight_toRightOf=”parent”</code> 이게 과연 무슨뜻일까요? 
정답은, button1 right의 constraint를 줄건데, 이건 parent의 right이다! 라는 뜻입니다.
즉, <code>parentlayout 속성의 오른쪽으로 배치한다!</code></p>
<p><img src="https://images.velog.io/images/yunsung_/post/c0b5082a-cc75-4346-b75a-84eaa969d877/image.png" alt=""></p>
<p>여기에서 좌우일렬로 서로 붙어있는 버튼 2개를 배치하고싶을 땐 어떻게 해야할까요?
하나의 버튼에는 바로 위처럼 right를 주고, 다른 하나의 버튼에는 left를 설정해주면 될까요? 한번 해보겠습니다.</p>
<pre><code>&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;android.support.constraint.ConstraintLayout xmlns:android=&quot;http://schemas.android.com/apk/res/android&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;&gt;

  &lt;Button
    android:id=&quot;@+id/button1&quot;
    android:layout_width=&quot;wrap_content&quot;
    android:layout_height=&quot;wrap_content&quot;
    app:layout_constraintRight_toRightOf=&quot;parent&quot;/&gt;

  &lt;Button
    android:id=&quot;@+id/button2&quot;
    android:layout_width=&quot;wrap_content&quot;
    android:layout_height=&quot;wrap_content&quot;
    app:layout_constraintLeft_toLeftOf=&quot;parent&quot;/&gt;

&lt;/android.support.constraint.ConstraintLayout&gt;</code></pre><p><img src="https://images.velog.io/images/yunsung_/post/054fdf43-1ec8-4d04-92b0-56af25344417/image.png" alt=""></p>
<p>어라? 두개의 버튼이 서로 떨어져있네요? 우리의 목적은 버튼 두개를 붙여야하는데...
우리는 여기에서 한가지 개념을 더 공부할 필요가있습니다.
바로..</p>
<p>Chains입니다!</p>
<h3 id="chains">Chains</h3>
<blockquote>
<p>chain내의 뷰간에 공간을 공유하고 사용 가능한 공간을 분할하는 방식을 제어</p>
</blockquote>
<p>즉, 뷰 서로서로가 연결되어있다 정도로 이해하면 쉽습니다.</p>
<pre><code>&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;android.support.constraint.ConstraintLayout xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
  xmlns:app=&quot;http://schemas.android.com/apk/res-auto&quot;
  android:layout_width=&quot;match_parent&quot;
  android:layout_height=&quot;match_parent&quot;&gt;

  &lt;Button
    android:id=&quot;@+id/button1&quot;
    android:layout_width=&quot;wrap_content&quot;
    android:layout_height=&quot;wrap_content&quot;
    app:layout_constraintRight_toRightOf=&quot;parent&quot;
    app:layout_constraintLeft_toRightOf=&quot;@+id/button2&quot; /&gt;

  &lt;Button
    android:id=&quot;@+id/button2&quot;
    android:layout_width=&quot;wrap_content&quot;
    android:layout_height=&quot;wrap_content&quot;
    app:layout_constraintLeft_toLeftOf=&quot;parent&quot;
    app:layout_constraintRight_toLeftOf=&quot;@id/button1&quot; /&gt;

&lt;/android.support.constraint.ConstraintLayout&gt;</code></pre><p>이 코드는 위의 코드와 다르게 @id값을 참조하고 있습니다. button1의 왼쪽은 부모대신 button2의 오른쪽에 있고, button2의 오른쪽은 부모대신 button1의 왼쪽에 있게 하는 
설정값을 준것입니다.</p>
<p><img src="https://images.velog.io/images/yunsung_/post/aef4f094-f372-45fb-92be-0b928a43a9f5/image.png" alt=""></p>
<p>실제로, 두 뷰를 서로 연결해주게 되면, 가운데에 다음과같은 체인모양이 형성됩니다!
하지만 두개의 상호관계를 정해주었긴해도 아직 두 뷰가 붙진 않았습니다..
여기서 chainStyle을 알고가셔야 합니다.</p>
<p><img src="https://images.velog.io/images/yunsung_/post/08f68e07-0296-443d-aae3-f4fd2a8c2fc5/image.png" alt=""></p>
<p>여기서 우리는 버튼을 붙여야 하니까 3번을 선택해줍니다!</p>
<pre><code>&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;android.support.constraint.ConstraintLayout xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
  xmlns:app=&quot;http://schemas.android.com/apk/res-auto&quot;
  android:layout_width=&quot;match_parent&quot;
  android:layout_height=&quot;match_parent&quot;&gt;

  &lt;Button
    android:id=&quot;@+id/button1&quot;
    android:layout_width=&quot;wrap_content&quot;
    android:layout_height=&quot;wrap_content&quot;
    android:text=&quot;button1&quot;
    app:layout_constraintRight_toRightOf=&quot;parent&quot;
    app:layout_constraintLeft_toRightOf=&quot;@+id/button2&quot; /&gt;

  &lt;Button
    android:id=&quot;@+id/button2&quot;
    android:text=&quot;button2&quot;
    android:layout_width=&quot;wrap_content&quot;
    android:layout_height=&quot;wrap_content&quot;
    app:layout_constraintHorizontal_chainStyle=&quot;packed&quot;
    app:layout_constraintLeft_toLeftOf=&quot;parent&quot;
    app:layout_constraintRight_toLeftOf=&quot;@id/button1&quot; /&gt;

&lt;/android.support.constraint.ConstraintLayout&gt;</code></pre><p><img src="https://images.velog.io/images/yunsung_/post/7b7bc559-da5d-436f-885d-466f01e792de/image.png" alt=""></p>
<p>우와! 드디어 두개의 버튼이 붙었네요! 하지만 가운데로 정렬이 되어있습니다. 그이유는
각 버튼의 오른쪽과 왼쪽이 parent가 기준으로 되어있기 때문입니다.</p>
<p>이때 왼쪽으로 붙이고싶다면? </p>
<pre><code>app:layout_constraintHorizontal_bias=&quot;0&quot; </code></pre><p>이라는 코드만 추가하시면 됩니다. default값은 기본적으로 0.5로 설정되어있고
default값보다 작으면 왼쪽, 크면 오른쪽으로 배치됩니다.</p>
<h3 id="tip">Tip</h3>
<pre><code>&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;android.support.constraint.ConstraintLayout xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
  xmlns:app=&quot;http://schemas.android.com/apk/res-auto&quot;
  android:layout_width=&quot;match_parent&quot;
  android:layout_height=&quot;match_parent&quot;&gt;

  &lt;TextView
    android:id=&quot;@+id/text1&quot;
    android:text=&quot;button2button2button2button2&quot;
    android:layout_width=&quot;wrap_content&quot;
    android:layout_height=&quot;wrap_content&quot;
    android:maxLines=&quot;1&quot;
    app:layout_constrainedWidth=&quot;true&quot;
    app:layout_constraintHorizontal_weight=&quot;1&quot;
    app:layout_constraintHorizontal_bias=&quot;0&quot;
    app:layout_constraintHorizontal_chainStyle=&quot;packed&quot;
    app:layout_constraintStart_toStartOf=&quot;parent&quot;
    app:layout_constraintEnd_toStartOf=&quot;@id/button1&quot; /&gt;

  &lt;Button
    android:id=&quot;@+id/button1&quot;
    android:layout_width=&quot;wrap_content&quot;
    android:layout_height=&quot;wrap_content&quot;
    android:text=&quot;button1&quot;
    app:layout_constraintEnd_toEndOf=&quot;parent&quot;
    app:layout_constraintStart_toEndOf=&quot;@id/text1&quot; /&gt;

&lt;/android.support.constraint.ConstraintLayout&gt;</code></pre><blockquote>
<p>dth를 0dp로 설정하고 weight를 1로 설정해도 버튼이 계속 사라지는 문제</p>
</blockquote>
<p>android:layout_width=”wrap_content”와 app:layout_constrainedWidth=”true” 으로 해결</p>
]]></description>
        </item>
    </channel>
</rss>