<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Hwi_Chance.log</title>
        <link>https://velog.io/</link>
        <description>안드로이드 개발자를 꿈꾸는 사람</description>
        <lastBuildDate>Mon, 06 Sep 2021 09:47:48 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>Hwi_Chance.log</title>
            <url>https://images.velog.io/images/hwi_chance/profile/b1121de7-ab8f-4ef8-8be8-f0426d69dea6/IMG_20181228_175253338.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. Hwi_Chance.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/hwi_chance" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[Android] Data Binding - Part.2]]></title>
            <link>https://velog.io/@hwi_chance/Android-Data-Binding-Part.2</link>
            <guid>https://velog.io/@hwi_chance/Android-Data-Binding-Part.2</guid>
            <pubDate>Mon, 06 Sep 2021 09:47:48 GMT</pubDate>
            <description><![CDATA[<h2 id="📌-식별-가능한-데이터-객체와-data-binding">📌 식별 가능한 데이터 객체와 Data Binding</h2>
<p><strong>Data Binding</strong>을 통해 객체, 필드 또는 컬렉션을 식별 가능하게 만들 수 있습니다.</p>
<blockquote>
<p><strong>식별 가능성</strong>
객체가 데이터 변경에 관해 다른 객체에 알릴 수 있는 기능</p>
</blockquote>
<p>Data Binding을 사용하면 데이터 변경 시 리스너에 알리는 기능을 데이터 객체에 제공할 수 있는데, 이를 통해 UI를 자동으로 업데이트할 수 있습니다.</p>
<h3 id="식별-가능한-필드">식별 가능한 필드</h3>
<p>필드는 일반 <code>Observable</code> 클래스 및 <code>Observable Primitive</code> 클래스를 사용하여 식별 가능하게 만들 수 있습니다.</p>
<p>식별 가능한 필드는 단일 필드가 있는 독립적 객체입니다. <code>Primitive</code> 버전은 액세스 작업 중에 박싱 및 언박싱을 방지해야 하기 때문에 읽기 전용 속성으로 만들어야 합니다.</p>
<pre><code class="language-kotlin">class User {
    val firstName = ObservableField&lt;String&gt;()
    val lastName = ObservableField&lt;String&gt;()
    val age = ObservableInt()
}</code></pre>
<h3 id="식별-가능한-컬렉션">식별 가능한 컬렉션</h3>
<p>식별 가능한 컬렉션은 키를 통해 접근할 수 있습니다. </p>
<p>키가 참조 유형일 때는 <code>ObservableArrayMap</code> 클래스가 유용합니다.</p>
<pre><code class="language-kotlin">ObservableArrayMap&lt;String, Any&gt;().apply {
    put(&quot;firstName&quot;, &quot;Google&quot;)
    put(&quot;lastName&quot;, &quot;Inc.&quot;)
    put(&quot;age&quot;, 17)
}    </code></pre>
<pre><code class="language-xml">&lt;data&gt;
    &lt;import type=&quot;android.databinding.ObservableMap&quot; /&gt;
    &lt;variable name=&quot;user&quot; type=&quot;ObservableMap&lt;String, Object&gt;&quot; /&gt;
&lt;/data&gt;

&lt;TextView
    android:text=&quot;@{user[&#39;lastName&#39;]}&quot;
    android:layout_width=&quot;wrap_content&quot;
    android:layout_height=&quot;wrap_content&quot; /&gt;</code></pre>
<p>키가 정수일 때는 <code>ObsevableArrayList</code> 클래스가 유용합니다.</p>
<pre><code class="language-kotlin">ObservableArrayList&lt;Any&gt;().apply {
    add(&quot;Google&quot;)
    add(&quot;Inc.&quot;)
    add(17)
} </code></pre>
<pre><code class="language-xml">&lt;data&gt;
    &lt;import type=&quot;android.databinding.ObservableList&quot;/&gt;
    &lt;import type=&quot;com.example.my.app.Fields&quot;/&gt;
    &lt;variable name=&quot;user&quot; type=&quot;ObservableList&lt;Object&gt;&quot;/&gt;
&lt;/data&gt;

&lt;TextView
    android:text=&#39;@{String.valueOf(1 + (Integer)user[Fields.AGE])}&#39;
    android:layout_width=&quot;wrap_content&quot;
    android:layout_height=&quot;wrap_content&quot;/&gt;</code></pre>
<h3 id="식별-가능한-객체">식별 가능한 객체</h3>
<p><code>Observable</code> 인터페이스를 구현하면 식별 가능한 객체의 변경에 관한 알림을 받는 리스너를 등록할 수 있습니다. </p>
<p>Data Binding 라이브러리는 리스너 등록 메커니즘을 구현하는 <code>BaseObservable</code> 클래스를 제공합니다. </p>
<pre><code class="language-kotlin">class User : BaseObservable() {

    @get:Bindable
    var firstName: String = &quot;&quot;
        set(value) {
            field = value
            notifyPropertyChanged(BR.firstName)
        }

    @get:Bindable
    var lastName: String = &quot;&quot;
        set(value) {
            field = value
            notifyPropertyChanged(BR.lastName)
        }
}</code></pre>
<p>Data Binding은 데이터 바인딩에 사용된 리소스의 ID를 포함하는 모듈 패키지에 이름이 <code>BR</code>인 클래스를 생성합니다. <code>Bindable</code> 주석은 컴파일하는 동안 <code>BR</code> 클래스 파일에 항목을 생성합니다.</p>
<h2 id="📌-바인딩-어댑터">📌 바인딩 어댑터</h2>
<p>바인딩 어댑터는 적절한 프레임워크를 호출하여 값을 설정하는 작업을 담당합니다. </p>
<h3 id="속성-값-설정">속성 값 설정</h3>
<p>결합된 값이 변경될 때마다 생성된 결합 클래스는 결합 표현식을 사용하여 속성 값을 설정해야 합니다.</p>
<h4 id="자동-메서드-선택">자동 메서드 선택</h4>
<p>라이브러리는 속성의 이름과 타입을 토대로 관련된 setter 메서드를 찾습니다. 예를 들어 <code>android:text=&quot;@{user.name}&quot;</code> 표현식이 있는 경우 라이브러리는 <code>user.getName()</code>에서 반환한 타입을 허용하는 <code>setText(arg)</code> 메서드를 찾습니다. </p>
<h4 id="맞춤-메서드-이름-지정">맞춤 메서드 이름 지정</h4>
<p>일부 속성에는 이름이 일치하지 않는 setter가 있습니다. 이러한 상황에서 속성은 <code>BindingMethods</code> 주석을 사용하여 setter와 연결될 수도 있습니다. 주석은 클래스와 함께 사용되며 이름이 바뀐 각 메서드에 하나씩 여러 <code>BindingMethod</code> 주석을 포함할 수 있습니다.</p>
<pre><code class="language-kotlin">@BindingMethods(value = [
    BindingMethod(
        type = android.widget.ImageView::class,
        attribute = &quot;android:tint&quot;,
        method = &quot;setImageTintList&quot;)])</code></pre>
<p>위 예에서 <code>android:tint</code> 속성은 <code>setTint()</code> 메서드가 아닌 <code>setImageTintList(ColorStateList)</code> 메서드와 연결됩니다.</p>
<h4 id="맞춤-로직-제공">맞춤 로직 제공</h4>
<p>일부 속성에는 맞춤 결합 로직이 필요합니다. <code>BindingAdapter</code> 주석이 있는 정적 바인딩 어댑터 메서드를 사용하면 속성의 setter가 호출되는 방식을 맞춤설정할 수 있습니다.</p>
<pre><code class="language-kotlin">@BindingAdapter(&quot;android:paddingLeft&quot;)
fun setPaddingLeft(view: View, padding: Int) {
    view.setPadding(padding,
                view.getPaddingTop(),
                view.getPaddingRight(),
                view.getPaddingBottom())
}</code></pre>
<p>바인딩 어댑터 메서드에서 첫 번째 매개변수는 속성과 연결된 뷰의 타입을 결정합니다. 두 번째 매개변수는 지정된 속성의 결합 표현식에서 허용되는 타입을 결정합니다.</p>
<p>개발자가 정의하는 바인딩 어댑터는 충돌이 발생하면 Android 프레임워크에서 제공하는 기본 어댑터보다 우선 적용됩니다.</p>
<p>여러 속성을 받는 어댑터도 있을 수 있습니다.</p>
<pre><code class="language-kotlin">@BindingAdapter(&quot;imageUrl&quot;, &quot;error&quot;)
fun loadImage(view: ImageView, url: String, error: Drawable) {
    Picasso.get().load(url).error(error).into(view)
}</code></pre>
<p>속성이 하나라도 설정될 때 어댑터가 호출되도록 하려면 다음 예에서와 같이 어댑터의 선택적 <code>requireAll</code> 플래그를 <code>false</code>로 설정할 수 있습니다.</p>
<pre><code class="language-kotlin">@BindingAdapter(value = [&quot;imageUrl&quot;, &quot;placeholder&quot;], requireAll = false)
fun setImageUrl(imageView: ImageView, url: String?, placeHolder: Drawable?) {
    if (url == null) {
        imageView.setImageDrawable(placeholder);
    } else {
        MyImageLoader.loadInto(imageView, url, placeholder);
    }
}</code></pre>
<p>바인딩 어댑터 메서드는 선택적으로 핸들러의 이전 값을 사용할 수 있습니다. 이전 값과 새 값을 사용하는 메서드는 아래 예에서와 같이 속성의 모든 이전 값을 먼저 선언한 후 새 값을 선언해야 합니다.</p>
<pre><code class="language-kotlin">    @BindingAdapter(&quot;android:paddingLeft&quot;)
    fun setPaddingLeft(view: View, oldPadding: Int, newPadding: Int) {
        if (oldPadding != newPadding) {
            view.setPadding(padding,
                        view.getPaddingTop(),
                        view.getPaddingRight(),
                        view.getPaddingBottom())
        }
    }</code></pre>
<p>이벤트 핸들러는 다음 예에서와 같이 하나의 추상 메서드가 있는 인터페이스 또는 추상 클래스에서만 사용할 수 있습니다.</p>
<pre><code class="language-kotlin">@BindingAdapter(&quot;android:onLayoutChange&quot;)
fun setOnLayoutChangeListener(
        view: View,
        oldValue: View.OnLayoutChangeListener?,
        newValue: View.OnLayoutChangeListener?
) {
    if (Build.VERSION.SDK_INT &gt;= Build.VERSION_CODES.HONEYCOMB) {
        if (oldValue != null) {
                view.removeOnLayoutChangeListener(oldValue)
        }
        if (newValue != null) {
            view.addOnLayoutChangeListener(newValue)
        }
    }
}</code></pre>
<h2 id="reference">Reference</h2>
<ul>
<li><a href="https://developer.android.com/topic/libraries/data-binding">Android Developers - DataBinding</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Android] Data Binding - Part.1]]></title>
            <link>https://velog.io/@hwi_chance/Android-Data-Binding-Part.1</link>
            <guid>https://velog.io/@hwi_chance/Android-Data-Binding-Part.1</guid>
            <pubDate>Mon, 06 Sep 2021 07:50:07 GMT</pubDate>
            <description><![CDATA[<h2 id="📌-data-binding">📌 Data Binding</h2>
<p><strong>Data Binding</strong>은 UI 구성요소와 앱의 데이터 소스를 선언적으로 연결할 수 있게 하는 라이브러리입니다. </p>
<p>레이아웃 파일에서 UI 구성요소를 앱 데이터와 연결하면 액티비티에서 UI 프레임워크의 호출을 줄일 수 있어서 코드가 간결해지고 유지관리가 쉬워진다는 장점이 있습니다. 또한 앱 성능이 향상되며, 메모리 누수 및 Null Pointer 예외를 방지할 수 있습니다. </p>
<h3 id="레이아웃-파일">레이아웃 파일</h3>
<p>Data Binding 라이브러리는 레이아웃의 뷰를 데이터 객체와 결합하는데 필요한 클래스를 자동으로 생성합니다.</p>
<h4 id="레이아웃-파일-작성">레이아웃 파일 작성</h4>
<p>Data Binding을 사용하는 레이아웃 파일은 <code>layout</code>이라는 루트 태그로 시작하고 그 안에 해당 레이아웃에서 사용할 데이터를 <code>data</code> 태그를 통해 명시합니다. 그런 다음 레이아웃을 구성할 뷰들을 배치합니다. </p>
<pre><code class="language-xml">&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;&gt;
   &lt;data&gt;
       &lt;variable name=&quot;user&quot; type=&quot;com.example.User&quot;/&gt;
   &lt;/data&gt;
   &lt;LinearLayout
       android:orientation=&quot;vertical&quot;
       android:layout_width=&quot;match_parent&quot;
       android:layout_height=&quot;match_parent&quot;&gt;
       &lt;TextView 
           android:layout_width=&quot;wrap_content&quot;
           android:layout_height=&quot;wrap_content&quot;
           android:text=&quot;@{user.firstName}&quot;/&gt;
       &lt;TextView 
           android:layout_width=&quot;wrap_content&quot;
           android:layout_height=&quot;wrap_content&quot;
           android:text=&quot;@{user.lastName}&quot;/&gt;
   &lt;/LinearLayout&gt;
&lt;/layout&gt;
</code></pre>
<p>앱의 데이터는 <code>@{}</code> 구문을 통해 뷰의 특정 속성에 지정됩니다. </p>
<h4 id="표현식">표현식</h4>
<p><code>@{}</code> 표현식에는 <code>this</code>, <code>super</code>, <code>new</code>, 명시적 제네릭 호출을 제외한 여러 연산자와 키워드를 사용할 수 있습니다.</p>
<pre><code class="language-xml">&lt;TextView
    android:text=&quot;@{String.valueOf(index + 1)}&quot;
    android:visibility=&quot;@{age &gt; 13 ? View.GONE : View.VISIBLE}&quot; /&gt;
</code></pre>
<p>또한 표현식에는 Null 병합 연산자를 사용할 수 있습니다. </p>
<pre><code class="language-xml">&lt;TextView
    android:text=&quot;@{user.displayName ?? user.lastName}&quot; /&gt;</code></pre>
<blockquote>
<p><strong>Null 병합 연산자</strong>
왼쪽 피연산자가 NULL이 아니면 왼쪽 피연산자를 선택, NULL이면 오른쪽 피연산자를 선택</p>
</blockquote>
<p>표현식은 ID를 통해 레이아웃의 다른 뷰를 참조할 수도 있습니다.</p>
<pre><code class="language-xml">&lt;EditText
    android:id=&quot;@+id/example_text&quot;
    android:layout_height=&quot;wrap_content&quot;
    android:layout_width=&quot;match_parent&quot;/&gt;
&lt;TextView
    android:id=&quot;@+id/example_output&quot;
    android:layout_width=&quot;wrap_content&quot;
    android:layout_height=&quot;wrap_content&quot;
    android:text=&quot;@{exampleText.text}&quot;/&gt; </code></pre>
<h3 id="데이터-결합">데이터 결합</h3>
<p>Data Binding도 각 레이아웃 파일의 바인딩 클래스를 생성하는데요, 이 클래스는 View Binding처럼 레이아웃 파일 이름을 파스칼 표기법으로 변환한 뒤, Binding이라는 접미사를 추가한 이름을 갖습니다. </p>
<p>이 바인딩 클래스에는 레이아웃 속성(데이터 변수 등)에서부터 레이아웃 뷰까지 모든 바인딩을 갖고 있으며, 어떻게 바인딩 표현식의 값을 할당할지도 알고 있습니다. </p>
<h4 id="바인딩-객체-생성-방법">바인딩 객체 생성 방법</h4>
<p>권장되는 결합 생성 방법은 레이아웃이 <code>inflating</code>을 하는 동안에 생성하는 것입니다.</p>
<pre><code class="language-kotlin">override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    val binding: ActivityMainBinding = DataBindingUtil.setContentView(
            this, R.layout.activity_main)

    binding.user = User(&quot;Test&quot;, &quot;User&quot;)
}</code></pre>
<p>다른 방법으로는 <code>LayoutInflater</code>를 이용하는 것이 있습니다. </p>
<pre><code class="language-kotlin">val binding: ActivityMainBinding = ActivityMainBinding.inflate(getLayoutInflater())    </code></pre>
<p><code>Fragment</code>, <code>ListView</code>, <code>RecyclerView</code> 어댑터 내에서 Data Binding을 사용한다면 <code>inflate()</code> 메서드를 사용하여 바인딩 객체를 생성할 수 있습니다.</p>
<pre><code class="language-kotlin">val listItemBinding = ListItemBinding.inflate(layoutInflater, viewGroup, false)
// or
val listItemBinding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false)</code></pre>
<h3 id="이벤트-처리">이벤트 처리</h3>
<p>Data Binding을 사용하면 뷰에서 전달되는 표현식 처리 이벤트를 작성할 수 있습니다. 이벤트 속성의 이름은 대부분 리스너 메서드의 이름에 따라 결정됩니다. </p>
<h4 id="메서드-참조">메서드 참조</h4>
<p>Data Binding에서 표현식이 메서드 참조로 계산되면 리스너에서 메서드 참조 및 소유자 객체를 래핑하고, 타겟 뷰에서 이 리스너를 설정합니다. </p>
<p>이벤트는 <code>android:onClick</code>이 액티비티의 메서드에 할당되는 방식과 유사하게 핸들러 메서드에 직접 결합될 수 있는데요, Data Binding의 메서드 참조는 표현식이 컴파일 타임에 처리된다는 장점이 있습니다.</p>
<pre><code class="language-kotlin">class MyHandlers {
    fun onClickFriend(view: View) { ... }
}</code></pre>
<pre><code class="language-xml">&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;&gt;
   &lt;data&gt;
       &lt;variable name=&quot;handlers&quot; type=&quot;com.example.MyHandlers&quot; /&gt;
       &lt;variable name=&quot;user&quot; type=&quot;com.example.User&quot; /&gt;
   &lt;/data&gt;
   &lt;LinearLayout
       android:orientation=&quot;vertical&quot;
       android:layout_width=&quot;match_parent&quot;
       android:layout_height=&quot;match_parent&quot;&gt;
       &lt;TextView
           android:layout_width=&quot;wrap_content&quot;
           android:layout_height=&quot;wrap_content&quot;
           android:text=&quot;@{user.firstName}&quot;
           android:onClick=&quot;@{handlers::onClickFriend}&quot; /&gt;
   &lt;/LinearLayout&gt;
&lt;/layout&gt;</code></pre>
<p>메서드의 매개변수는 이벤트 리스너의 매개변수와 일치해야 합니다.</p>
<p>메서드 참조이 리스너 결합과 다른 점은 실제 리스너 구현이 이벤트가 트리거될 때가 아닌, 데이터가 결합될 때 생성된다는 것입니다. 따라서 이벤트가 발생할 때 표현식을 계산하려면 리스너 결합을 사용해야 합니다.</p>
<h4 id="리스너-결합">리스너 결합</h4>
<p>리스너 결합은 메서드 참조와 달리 이벤트가 발생할 때 실행되는 결합 표현식이며, 메서드와 이벤트 리스너의 반환 값만 일치하면 됩니다. </p>
<pre><code class="language-kotlin">class Presenter {
    fun onSaveClick(task: Task){}
}</code></pre>
<pre><code class="language-xml">&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;&gt;
    &lt;data&gt;
        &lt;variable name=&quot;task&quot; type=&quot;com.android.example.Task&quot; /&gt;
        &lt;variable name=&quot;presenter&quot; type=&quot;com.android.example.Presenter&quot; /&gt;
    &lt;/data&gt;
    &lt;LinearLayout 
        android:layout_width=&quot;match_parent&quot; 
        android:layout_height=&quot;match_parent&quot;&gt;
        &lt;Button
            android:layout_width=&quot;wrap_content&quot; android:layout_height=&quot;wrap_content&quot;
            android:onClick=&quot;@{() -&gt; presenter.onSaveClick(task)}&quot; /&gt;
    &lt;/LinearLayout&gt;
&lt;/layout&gt;</code></pre>
<p>표현식에 콜백을 사용하면 데이터 결합은 필요한 리스너를 자동으로 생성하여 이벤트에 등록합니다. </p>
<p>리스너 결합에서는 모든 매개변수를 무시하거나, 모든 매개변수의 이름을 지정하여 매개변수를 선택할 수 있습니다. 매개변수 이름을 지정하면 표현식에 매개변수를 사용할 수 있습니다.</p>
<pre><code class="language-xml">&lt;Button
    android:layout_width=&quot;wrap_content&quot; android:layout_height=&quot;wrap_content&quot;
    android:onClick=&quot;@{(theView) -&gt; presenter.onSaveClick(theView, task)}&quot; /&gt;</code></pre>
<pre><code class="language-kotlin">class Presenter {
    fun onSaveClick(view: View, task: Task){}
}</code></pre>
<h3 id="import-variable-include"><code>import</code>, <code>variable</code>, <code>include</code></h3>
<ul>
<li><code>import</code>를 사용하면 레이아웃 파일 내에서 클래스를 참조할 수 있습니다. </li>
<li><code>variable</code>을 사용하면 결합 표현식에 사용할 수 있는 속성을 설명할 수 있습니다.</li>
<li><code>include</code>를 사용하면 앱 전체에서 복잡한 레이아웃을 재사용할 수 있습니다. </li>
</ul>
<h4 id="import"><code>import</code></h4>
<p><code>data</code> 태그 내에 0개 이상의 <code>import</code> 요소를 사용할 수 있습니다.</p>
<pre><code class="language-xml">&lt;data&gt;
    &lt;import type=&quot;android.view.View&quot; /&gt;
&lt;/data&gt;

&lt;TextView
   android:text=&quot;@{user.lastName}&quot;
   android:layout_width=&quot;wrap_content&quot;
   android:layout_height=&quot;wrap_content&quot;
   android:visibility=&quot;@{user.isAdult ? View.VISIBLE : View.GONE}&quot; /&gt;</code></pre>
<p>이처럼 <code>import</code>를 통해 클래스를 가져오면 표현식에서 해당 클래스를 참조할 수 있습니다.</p>
<p>별칭을 사용하여 클래스 이름 충돌을 해결할 수 있습니다.</p>
<pre><code class="language-kotlin">&lt;import type=&quot;android.view.View&quot;/&gt;
&lt;import type=&quot;com.example.real.estate.View&quot;
        alias=&quot;Vista&quot;/&gt;</code></pre>
<p>가져온 클래스는 변수 및 표현식에서 유형 참조로 사용할 수도 있습니다</p>
<pre><code class="language-xml">&lt;data&gt;
    &lt;import type=&quot;com.example.User&quot; /&gt;
    &lt;import type=&quot;java.util.List&quot; /&gt;
    &lt;variable name=&quot;user&quot; type=&quot;User&quot; /&gt;
    &lt;variable name=&quot;userList&quot; type=&quot;List&amp;lt;User&gt;&quot; /&gt;
&lt;/data&gt;</code></pre>
<p>형변환 또한 가능합니다.</p>
<pre><code class="language-xml">&lt;TextView
   android:text=&quot;@{((User)(user.connection)).lastName}&quot;
   android:layout_width=&quot;wrap_content&quot;
   android:layout_height=&quot;wrap_content&quot; /&gt;</code></pre>
<h4 id="variable"><code>variable</code></h4>
<p><code>data</code> 태그 내에 여러 <code>variable</code> 요소를 사용할 수 있습니다. </p>
<pre><code class="language-xml">&lt;data&gt;
    &lt;import type=&quot;android.graphics.drawable.Drawable&quot;/&gt;
    &lt;variable name=&quot;user&quot; type=&quot;com.example.User&quot;/&gt;
    &lt;variable name=&quot;image&quot; type=&quot;Drawable&quot;/&gt;
    &lt;variable name=&quot;note&quot; type=&quot;String&quot;/&gt;
&lt;/data&gt;</code></pre>
<h4 id="include"><code>include</code></h4>
<p>속성에 앱 네임스페이스 및 변수 이름을 사용함으로써 포함하는 레이아웃에서 포함된 레이아웃의 결합으로 변수를 전달할 수 있습니다. </p>
<pre><code class="language-xml">&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:bind=&quot;http://schemas.android.com/apk/res-auto&quot;&gt;
   &lt;data&gt;
       &lt;variable name=&quot;user&quot; type=&quot;com.example.User&quot;/&gt;
   &lt;/data&gt;
   &lt;LinearLayout
       android:orientation=&quot;vertical&quot;
       android:layout_width=&quot;match_parent&quot;
       android:layout_height=&quot;match_parent&quot;&gt;
       &lt;include 
            layout=&quot;@layout/name&quot;
            bind:user=&quot;@{user}&quot;/&gt;
       &lt;include 
            layout=&quot;@layout/contact&quot;
            bind:user=&quot;@{user}&quot;/&gt;
   &lt;/LinearLayout&gt;
&lt;/layout&gt;
</code></pre>
<h2 id="reference">Reference</h2>
<ul>
<li><a href="https://developer.android.com/topic/libraries/data-binding">Android Developers - DataBinding</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Android] View Binding]]></title>
            <link>https://velog.io/@hwi_chance/Android-ViewBinding</link>
            <guid>https://velog.io/@hwi_chance/Android-ViewBinding</guid>
            <pubDate>Mon, 30 Aug 2021 07:56:20 GMT</pubDate>
            <description><![CDATA[<h2 id="📌-view-binding">📌 View Binding</h2>
<p><strong>View Binding</strong>은 뷰와 상호작용하는 코드를 쉽게 작성할 수 있도록 만들어 주는 기능입니다.</p>
<p>모듈에서 View Binding을 사용하도록 설정하면 모듈에 있는 <code>XML</code> 레이아웃 파일들의 바인딩 클래스가 생성됩니다. 이 바인딩 클래스의 인스턴스에는 해당 레이아웃에서 ID를 가지고 있는 모든 뷰와 루트 뷰의 직접 참조가 포함되어 있습니다. 그래서 대부분의 경우 View Binding은 <code>findViewById</code>를 대체할 수 있습니다.</p>
<h3 id="모듈에서-view-binding-사용-설정">모듈에서 View Binding 사용 설정</h3>
<p>View Binding은 모듈 단위로 사용 설정되며, <code>build.gradle</code> 파일에 View Binding 사용 여부를 명시합니다.</p>
<pre><code>// android studio 4.0 이하
android {
    viewBinding {
        enabled = true
    }
}

---------------------------

// android studio 4.0 이상
android { 
    buildFeatures { 
        viewBinding = true 
    }
}</code></pre><h4 id="특정-레이아웃-무시">특정 레이아웃 무시</h4>
<p>특정 레이아웃의 바인딩 클래스를 생성하지 않으려면 <code>tools:viewBindingIgnore=&quot;true&quot;</code> 속성을 루트 뷰에 추가해야 합니다.</p>
<pre><code class="language-xml">&lt;LinearLayout
        tools:viewBindingIgnore=&quot;true&quot;&gt;
&lt;/LinearLayout&gt;</code></pre>
<h3 id="바인딩-클래스">바인딩 클래스</h3>
<p>모듈에 View Binding 사용하도록 설정되면 <code>XML</code> 레이아웃 파일의 이름을 파스칼 표기법으로 변환하고 끝에 <code>Binding</code>을 추가한 이름을 가진 바인딩 클래스가 생성됩니다.</p>
<pre><code>&lt;LinearLayout ... &gt;
        &lt;TextView android:id=&quot;@+id/name&quot; /&gt;
        &lt;ImageView android:cropToPadding=&quot;true&quot; /&gt;
        &lt;Button android:id=&quot;@+id/btn&quot; /&gt;
&lt;/LinearLayout&gt;</code></pre><p>예를 들어 레이아웃 파일 이름이 <code>result_profile.xml</code>인 경우 <code>ResultProfileBinding</code>이라는 바인딩 클래스가 생성되며, 이 클래스에는 <code>name</code>이라는 <code>TextView</code> 필드와 <code>btn</code>이라는 <code>Button</code> 필드가 있습니다. <code>ImageView</code>는 ID가 없기 때문에 바인딩 클래스에 참조가 존재하지 않습니다.</p>
<h4 id="루트뷰-접근">루트뷰 접근</h4>
<p>모든 바인딩 클래스에는 해당 레이아웃의 루트 뷰에 관한 직접 참조를 제공하는 <code>getRoot()</code> 메서드가 포함되어 있습니다. 따라서 레이아웃의 루트 뷰는 ID가 없어도 바인딩 클래스를 통해 직접 접근할 수 있습니다.</p>
<h3 id="view-binding-사용">View Binding 사용</h3>
<h4 id="activity에서-view-binding-사용"><code>Activity</code>에서 View Binding 사용</h4>
<p>생성된 바인딩 클래스의 <code>inflate()</code> 메서드를 호출하면 바인딩 클래스의 인스턴스가 생성됩니다. </p>
<p>인스턴스가 생성되면 <code>getRoot()</code> 메서드를 통해 루트 뷰 참조를 가져오고 이 루트 뷰를 <code>onCreate()</code>에서 <code>setContentView()</code> 메서드에 전달하여 레이아웃을 활성 뷰로 만듭니다.</p>
<pre><code class="language-kotlin">private lateinit var binding: ResultProfileBinding

override fun onCreate(savedInstanceState: Bundle) {
    super.onCreate(savedInstanceState)
    binding = ResultProfileBinding.inflate(latoutInflater)
    val view = binding.root
    setContentView(view)
}</code></pre>
<p>이후에는 인스턴스를 사용하여 뷰를 참조합니다. </p>
<pre><code class="language-kotlin">binding.name.text = viewModel.name
binding.btn.setOnClickListener { viewModel.userClicked() }</code></pre>
<h4 id="fragment에서-view-binding-사용"><code>Fragment</code>에서 View Binding 사용</h4>
<p><code>Activity</code>에서 View Binding을 사용하는 것과 거의 동일하며, 활성 뷰로 만드는 부분에서만 차이가 있습니다. </p>
<p><code>Fragment</code>에서는 <code>onCreateView()</code>에서 루트 뷰를 반환하여 레이아웃을 활성 뷰로 만듭니다.</p>
<pre><code class="language-kotlin">private var _binding: ResultProfileBinding? = null
private val binding get() = _binding!!

override fun onCreateView(
    inflater: LayoutInflater,
    container: ViewGroup?,
    savedInstanceState: Bundle?
): View? {
    _binding = ResultProfileBinding.inflate(inflater, container, false)
    val view = binding.root
    return view
}

override fun onDestroyView() {
    super.onDestroyView()
    _binding = null
}</code></pre>
<h3 id="findviewbyid와의-차이점"><code>findViewById</code>와의 차이점</h3>
<h4 id="null-safety"><code>Null</code> Safety</h4>
<ul>
<li>View Binding은 뷰의 직접 참조를 생성하므로 유효하지 않은 뷰 ID로 인해 Null Pointer 예외가 발생하지 않습니다. </li>
<li>레이아웃에 포함된 뷰가 특정 조건에서만 존재하는 경우, 바인딩 클래스는 해당 필드를 <code>@Nullable</code>로 마크하여 참조 실수를 방지할 수 있게 해줍니다. </li>
</ul>
<h4 id="type-safety">Type Safety</h4>
<ul>
<li>각 바인딩 클래스에 있는 필드의 타입이 <code>XML 파일</code>에서 참조하는 뷰의 타입과 일치합니다. 즉, 클래스 변환 예외가 발생할 위험이 없습니다.</li>
</ul>
<h4 id="예외-탐지">예외 탐지</h4>
<ul>
<li>View Binding은 <code>Null Safety</code>하고 <code>Type Safety</code>하기 때문에 레이아웃과 코드 사이의 비호환성이 발생하면 <code>findViewById</code>와 달리 런타임이 아닌 컴파일 타임에 예외를 탐지할 수 있습니다. </li>
</ul>
<h3 id="data-binding과의-차이">Data Binding과의 차이</h3>
<p>View Binding과 Data Binding 모두 뷰를 직접 참조하는데 사용할 수 있는 바인딩 클래스를 생성합니다. 다만, View Binding은 보다 단순한 경우에 사용합니다. </p>
<h4 id="view-binding이-더-나은-점">View Binding이 더 나은 점</h4>
<ul>
<li>주석 처리가 필요하지 않기 때문에 컴파일 시간이 더 짧습니다.</li>
<li><code>XML</code>에 태그를 추가할 필요없고, 사용 설정하면 모든 레이아웃에 View Binding이 자동 적용되기 때문에 편리합니다. <h4 id="view-binding이-더-안좋은-점">View Binding이 더 안좋은 점</h4>
</li>
<li><code>XML</code> 레이아웃 파일에서 직접 동적 UI 컨텐츠를 선언하는데 사용할 수 없습니다.</li>
<li>양방향 데이터 바인딩을 지원하지 않습니다. </li>
</ul>
<h2 id="reference">Reference</h2>
<ul>
<li><a href="https://developer.android.com/topic/libraries/view-binding?hl=ko">Android Developers - ViewBinding</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Android] 안드로이드 Splash Screen 만들기]]></title>
            <link>https://velog.io/@hwi_chance/Android-%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-Splash-Screen-%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
            <guid>https://velog.io/@hwi_chance/Android-%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-Splash-Screen-%EB%A7%8C%EB%93%A4%EA%B8%B0</guid>
            <pubDate>Mon, 26 Jul 2021 11:39:17 GMT</pubDate>
            <description><![CDATA[<h2 id="🎨-액티비티-테마-사용">🎨 액티비티 테마 사용</h2>
<p>안드로이드 앱을 사용하다보면 종종 첫화면 전에 빈 화면이 잠깐 나오는 것을 볼 수 있는데요, 이는 앱을 새로 실행하는 경우 첫 화면을 그리는데 시간이 필요하기 때문입니다. </p>
<p>안드로이드 시스템은 이 시간 동안 비어있는 Placeholder Screen을 앱의 <code>windowBackground</code> color로 채우는데요, 액티비티 테마를 이용한다면 이 Placeholder Screen을 단순히 색깔만 가진 화면에서 <code>Splash Screen</code>으로 확장시킬 수 있습니다.</p>
<pre><code class="language-xml">&lt;!-- splash_drawable.xml --&gt;

&lt;layer-list xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;&gt;

  &lt;!-- background color --&gt;
  &lt;item android:drawable=&quot;@android:color/white&quot;/&gt;

  &lt;!-- App logo --&gt;
  &lt;item&gt;
      &lt;bitmap
          android:src=&quot;@drawable/app_logo&quot;
          android:gravity=&quot;center&quot;&gt;
      &lt;/bitmap&gt;
  &lt;/item&gt;

&lt;/layer-list&gt;</code></pre>
<p>먼저 <code>&lt;layer-list&gt;</code>를 통해 <code>Splash Screen</code>으로 사용될 <code>drawable</code>을 만듭니다. </p>
<pre><code class="language-xml">&lt;!-- styles.xml --&gt;

&lt;resources&gt;

  &lt;style name=&quot;SplashTheme&quot; parent=&quot;Theme.AppCompat.DayNight.NoActionBar&quot;&gt;
    &lt;item name=&quot;android:windowBackground&quot;&gt;@drawable/splash_drawable&lt;/item&gt;
  &lt;/style&gt;

&lt;/resources&gt;</code></pre>
<p>그런 다음 <code>Spash Screen</code>을 위한 테마를 만들고 <code>windowBackground</code> 속성에 앞에서 만든 <code>drawable</code>을 지정해줍니다.</p>
<pre><code class="language-xml">&lt;!-- AndroidManifest.xml --&gt;

&lt;manifest xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot; 
  package=&quot;com.~~~&quot;&gt;

  &lt;application
    android:icon=&quot;@mipmap/ic_launcher&quot;
    android:label=&quot;@string/app_name&quot;
    android:roundIcon=&quot;@mipmap/ic_launcher_round&quot;
    android:supportsRtl=&quot;true&quot;
    android:theme=&quot;@style/AppTheme&quot;&gt;

    &lt;activity android:name=&quot;.MainActivity&quot;
      android:theme=&quot;@style/SplashTheme&quot;&gt;
      &lt;intent-filter&gt;
        &lt;action android:name=&quot;android.intent.action.MAIN&quot; /&gt;

        &lt;category android:name=&quot;android.intent.category.LAUNCHER&quot; /&gt;
      &lt;/intent-filter&gt;
    &lt;/activity&gt;

  &lt;/application&gt;

&lt;/manifest&gt;</code></pre>
<p>만들어진 테마를 첫 화면이 될 액티비티에 적용하면 앱 실행 시 빈화면이 아닌 <code>Splash Screen</code>이 보여지게 됩니다.</p>
<pre><code class="language-kotlin">/* MainActivity.kt */

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        setTheme(R.style.AppTheme)
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }
}</code></pre>
<p>액티비티가 <code>Splash Screen</code>과는 다른 테마를 가져야 한다면, 해당 액티비티 <code>onCreate()</code>에서 <code>setTheme()</code> 메소드를 사용하여 <code>Splash Screen</code> 이후에는 기존 테마가 적용되록 만들어줘야 합니다. </p>
<p>이 방식을 이용한다면 첫 화면을 그리는 동안 빈 화면이 아닌 <code>Splash Screen</code>을 보여줌으로써 사용자 경험을 개선할 수 있지만, <code>Splash Screen</code>에 애니메이션이나 <code>Progress Bar</code>를 포함할 수는 없다는 단점이 있습니다. </p>
<h2 id="⌛-handler-runnable-그리고-timer">⌛ <code>Handler</code>, <code>Runnable</code> 그리고 <code>Timer</code></h2>
<p>많은 앱들은 <code>Splash Screen</code>을 위해 별도의 액티비티를 만들고 해당 액티비티에서 일정시간 대기한 후 로직에 따라 다른 액티비티로 이동하는 방식을 사용합니다. </p>
<p>이 방식은 주로 <code>Handler</code>와 <code>Runnable</code> 클래스를 통해 구현됩니다. </p>
<pre><code class="language-kotlin">/* MainActivity.kt */

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        Handler().postDelayed(2000) {
            var intent = Intent(this@MainActivity, NextActivity::class.java)
            startActivity(intent)
            finish()
        }
    }

    override fun onDestroy() {
        handler.removeCallbacksAndMessages(null)
        super.onDestroy()
    }
}</code></pre>
<p>이 방식은 <code>Splash Screen</code>에 애니메이션이나 <code>Progress Bar</code>를 사용할 수 있고, 좀 더 자유로운 <code>Splash Screen</code> 화면을 구성할 수 있습니다. 또한 특정 액티비티로 리다이렉션 하는 로직도 추가할 수 있습니다. </p>
<p>그러나 하드웨어의 뒤로가기 버튼을 누르는 경우 <code>handler</code>의 동작을 취소할 수 없고 예약된 동작으로 인해 화면이 랜덤하게 팝업된다는 단점이 있습니다. 이는 메모리 누수를 발생시키거나 데이터 손실을 유발할 수 있습니다. </p>
<p>이를 해결하기 위해서 <code>Timer</code> 클래스를 사용하기도 합니다. </p>
<pre><code class="language-kotlin">/* MainActivity.kt */

class MainActivity : AppCompatActivity() {

    var timer = Timer()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_timer_splash)

        timer.schedule(2000) {
            var intent = Intent(this@MainActivity, NextActivity::class.java)
            startActivity(intent)
            finish()
        }
    }

    override fun onPause() {
        timer.cancel()
        super.onPause()
    }
}</code></pre>
<p><code>Timer</code>는 <code>Handler</code>와 달리 동작을 취소할 수 있습니다. 다만 <code>Timer</code>는 완전히 새로운 쓰레드를 백그라운드에 생성하기 때문에 <code>TimerTask</code> 객체 안에서 UI와 상호작용할 수 없고, 메모리 측면에서 무거운 작업이며, 쓰레드 스위칭으로 인해 느리다는 단점이 있습니다. </p>
<h2 id="💡-coroutine-사용">💡 <code>Coroutine</code> 사용</h2>
<p><code>Handler</code>와 <code>Runnable</code> 클래스 대신에 코틀린의 <code>Coroutine</code>을 이용할 수도 있습니다.</p>
<pre><code class="language-kotlin">/* MainActivity.kt */

class MainActivity : AppCompatActivity() {

    val activityScope = CoroutineScope(Dispatchers.Main)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_coroutines_splash)

        activityScope.launch {
            delay(2000)

            var intent = Intent(this@MainActivity, NextActivity::class.java)
            startActivity(intent)
            finish()
        }
    }

    override fun onPause() {
        activityScope.cancel()
        super.onPause()
    }
}</code></pre>
<p>이 방식은 <code>CoroutineScope</code>가 <code>Dispatchers.Main</code>을 사용하기 때문에 작업을 <code>Main UI Thread</code>에서 수행하며, Thread Switching이 없습니다. 또한 코루틴의 동작을 취소할 수도 있습니다.</p>
<p>즉, <code>Coroutine</code>은 빠르고 안정적이며 메모리를 덜 쓰고, UI에 접근할 수 있다는 장점이 있습니다. </p>
<h2 id="reference"><code>Reference</code></h2>
<ul>
<li><a href="https://wajahatkarim.com/2019/12/revisited-a-guide-on-splash-screen-in-android-in-2020/">A Guide on Splash Screen in Android in 2020</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Git] Ch.7 Git 내부 동작 원리]]></title>
            <link>https://velog.io/@hwi_chance/Git-Ch.7-Git-%EB%82%B4%EB%B6%80-%EB%8F%99%EC%9E%91-%EC%9B%90%EB%A6%AC</link>
            <guid>https://velog.io/@hwi_chance/Git-Ch.7-Git-%EB%82%B4%EB%B6%80-%EB%8F%99%EC%9E%91-%EC%9B%90%EB%A6%AC</guid>
            <pubDate>Sat, 13 Mar 2021 08:12:51 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>팀 개발을 위한 Git, GitHub 시작하기</strong>(정호영, 진유림 지음)으로 공부한 내용을 정리한 글입니다.</p>
</blockquote>
<h2 id="git-add의-동작-원리">git add의 동작 원리</h2>
<h4 id="git-status">git status</h4>
<ul>
<li>워킹트리와 스테이지, 그리고 헤드 커밋에 저장된 파일의 차이를 비교해서 보여주는 명령어</li>
<li>새로 생성된 파일은 워킹트리에 저장됨</li>
</ul>
<h4 id="git-hash-object">git hash-object</h4>
<pre><code class="language-bash">$ git hash-object &lt;file name&gt;</code></pre>
<ul>
<li>파일의 <code>Checksum</code>을 확인하는 명령어</li>
<li>파일은 내용이 같다면 언제나 똑같은 <code>Checksum</code>을 가짐</li>
<li>다른 파일이지만 내용이 같다면 똑같은 <code>Checksum</code>을 가짐</li>
</ul>
<h4 id="git-add">git add</h4>
<ul>
<li>워킹트리에 존재하는 파일을 스테이지에 추가하는 명령어</li>
<li>최초로 <code>git add</code>를 실행하면 <code>.git</code> 폴더에 <code>index</code> 파일이 생성되는데, 이 <code>index</code> 파일이 <code>Git stage</code>임</li>
<li>또 <code>.git/objects</code> 폴더에 파일 <code>Checksum</code>의 앞 두자리를 폴더명으로 하는 폴더가 생성됨</li>
<li>해당 폴더에는 파일 <code>Checksum</code>의 앞 두자리를 제외한 문자열을 파일명으로 하는 <code>git object</code>가 생성됨</li>
<li>이 <code>git object</code>의 파일 형식은 <code>blob</code>임</li>
</ul>
<h2 id="git-commit의-동작-원리">git commit의 동작 원리</h2>
<h4 id="git-commit">git commit</h4>
<ul>
<li>스테이지의 내용으로 커밋을 생성하는 명령어</li>
<li><code>git commit</code>을 실행하면 <code>.git/objects</code> 폴더에 <code>git add</code> 때와 같은 방식으로 <code>git object</code>가 생성됨 </li>
<li>이 <code>git object</code>는 커밋 객체임</li>
<li>커밋 객체는 각자 고유한 <code>Checksum</code>을 가짐</li>
</ul>
<h4 id="commit-후-워킹트리-스테이지-헤드">commit 후 워킹트리, 스테이지, 헤드</h4>
<ul>
<li><code>commit</code> 후에 스테이지에서 파일이 제거될거라고 생각하기 쉬운데 사실을 그렇지 않음</li>
<li>워킹트리, 스테이지, 헤드에 속한 파일이 모두 똑같아짐 → 이를 <code>clean</code>하다고 함</li>
</ul>
<h4 id="git-tree-객체">Git tree 객체</h4>
<ul>
<li><code>git commit</code>을 실행하면 <code>.git/objects</code> 폴더에 <code>git tree object</code>도 생성됨</li>
<li>이 트리는 스테이지의 객체로 만들어진 트리임</li>
<li>스테이지의 객체가 동일하다면 트리 객체는 항상 동일한 <code>Checksum</code>을 가짐</li>
<li>트리 객체는 해당 트리를 생성한 커밋 객체에 포함됨</li>
</ul>
<h2 id="파일을-수정했을-때">파일을 수정했을 때</h2>
<h4 id="파일-수정">파일 수정</h4>
<ul>
<li>이미 한번 커밋한 파일을 수정하는 경우 워킹트리에 속한 파일의 <code>Checksum</code>만 바뀜</li>
<li>이 경우 파일은 <code>modified</code> 상태가 되는데, 이는 워킹트리 속 파일 <code>Checksum</code>이 스테이지 속 파일 <code>Checksum</code>과 다른 경우를 말함</li>
</ul>
<h4 id="수정된-파일-add">수정된 파일 add</h4>
<ul>
<li>수정된 파일을 <code>git add</code> 명령어를 통해 스테이지에 추가한 경우, 스테이지 속 파일의 <code>Checksum</code>이 워킹트리 속 파일의 <code>Checksum</code>과 같아지게 됨</li>
<li>이 경우 파일의 상태는 <code>staged</code>가 됨</li>
</ul>
<h2 id="중복-파일-관리">중복 파일 관리</h2>
<ul>
<li>Git에서 파일들은 <code>blob</code>으로 관리되며, 이 <code>blob</code>은 파일의 제목이나 생성 날짜와는 관계 없이 내용이 같을 경우 같은 <code>CheckSum</code>을 가짐</li>
<li>따라서 같은 내용을 같은 파일을 여러개 만들어도 Git은 하나의 <code>blob</code>으로 해당 파일들을 관리함</li>
<li>파일 목록은 <code>index</code> 파일에 <code>Checksum</code>과 함께 저장되어 있기 때문에, 동일한 내용을 같는 파일을 하나의 <code>blob</code>으로 관리할 수 있음</li>
</ul>
<h2 id="git-branch의-동작-원리">Git branch의 동작 원리</h2>
<h4 id="branch-생성">branch 생성</h4>
<ul>
<li><code>branch</code>를 생성하면 <code>.git/refs/heads</code> 폴더에 <code>branch</code>명으로 파일이 생성됨</li>
<li>해당 파일에는 그 <code>branch</code>가 가리키고 있는 커밋의 <code>Checksum</code>이 저장되어 있음</li>
</ul>
<h4 id="checkout">checkout</h4>
<ul>
<li>해당 <code>branch</code> 파일에 저장된 커밋으로 <code>HEAD</code>를 이동시킴</li>
<li>스테이지와 워킹트리를 <code>HEAD</code>가 가리키는 커밋과 동일한 내용으로 변경함</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Git] Ch.6 Git Branch]]></title>
            <link>https://velog.io/@hwi_chance/Git-Ch.6-Git-Branch</link>
            <guid>https://velog.io/@hwi_chance/Git-Ch.6-Git-Branch</guid>
            <pubDate>Sat, 13 Mar 2021 06:34:27 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>팀 개발을 위한 Git, GitHub 시작하기</strong>(정호영, 진유림 지음)으로 공부한 내용을 정리한 글입니다.</p>
</blockquote>
<h2 id="git-branch">Git Branch</h2>
<p><img src="https://images.velog.io/images/hwi_chance/post/9c0d207b-656b-4f02-b93c-b0f077b9b2a3/commit_tree.jpg" alt="Commit_tree"></p>
<h3 id="commit-tree">Commit tree</h3>
<ul>
<li>커밋 히스토리는 각 커밋들이 부모 커밋(<code>base commit</code>)을 가리키고 있는 트리 그림으로 표현할 수 있음</li>
<li>실제로 커밋을 하면 커밋 객체가 생성되는데, 이 커밋 객체에는 부모 커밋에 대한 참조와 실제 커밋을 구성하는 파일 객체가 들어 있음</li>
<li>부모 커밋은 자식 커밋에 대한 정보를 담고 있지 않음</li>
</ul>
<h3 id="branch">Branch</h3>
<ul>
<li>개념적으로 <code>branch</code>는 특정 커밋과 그 조상들을 지칭</li>
<li>실제로 <code>branch</code>는 단순히 커밋 객체 하나를 가리키는 포인터</li>
</ul>
<h4 id="branch-사용-이유">Branch 사용 이유</h4>
<ul>
<li>새로운 기능 추가 (<code>feature branch</code>)</li>
<li>버그 수정 (<code>hotFix branch</code> 혹은 <code>bugFix branch</code>)</li>
<li><code>merge</code>와 <code>rebase</code> 테스트</li>
<li>이전 코드 개선</li>
</ul>
<h4 id="head">HEAD</h4>
<ul>
<li>기본적으로 현재 작업 중인 <code>branch</code>의 최근 커밋을 가리키는 포인터</li>
<li><code>detached HEAD</code>: <code>checkout</code> 등에 의해 <code>branch</code>의 최근 커밋이 아닌 다른 커밋을 가리키는 헤드 </li>
<li><code>HEAD~&lt;숫자&gt;</code>: <code>HEAD~</code>는 헤드의 부모, <code>HEAD~n</code>은 헤드의 n번째 조상을 의미</li>
<li><code>HEAD^&lt;숫자&gt;</code>: <code>HEAD^</code>는 헤드의 부모, <code>HEAD^n</code>은 헤드의 n번째 부모를 의미. 병합 커밋처럼 부모가 둘 이상인 커밋에서만 의미가 있음</li>
</ul>
<h4 id="tag">Tag</h4>
<pre><code class="language-bash">$git tag -a -m &lt;message&gt; &lt;tag&gt; [branch or checksum]</code></pre>
<ul>
<li><code>-a</code>: 주석이 있는 태그 생성 옵션</li>
<li><code>branch</code>나 <code>checksum</code>을 생략하면 헤드에 태그 생성</li>
</ul>
<h3 id="3-way-merge">3-Way Merge</h3>
<p><img src="https://images.velog.io/images/hwi_chance/post/ae013694-dfe7-4b8a-9fc0-5c21055bbc14/1.jpg" alt="base"></p>
<ul>
<li>위 그림처럼 <code>feature branch</code>를 생성하여 다른 작업을 수행하던 중, 이전에 완료한 커밋의 코드를 수정해야할 상황이 발생</li>
</ul>
<p><img src="https://images.velog.io/images/hwi_chance/post/7b3a1c97-2e8a-4aaa-883b-7363fc44e3b0/2.jpg" alt="hot_fix"></p>
<ul>
<li>수정할 코드가 있는 커밋에서 <code>hotFix branch</code>를 생성하여 코드 수정</li>
</ul>
<p><img src="https://images.velog.io/images/hwi_chance/post/9f4b9ac8-4351-46cd-b369-25fd76ced429/3.jpg" alt="fast_forward"></p>
<ul>
<li><code>hotFix branch</code>를 <code>master branch</code>에 병합</li>
<li><code>master branch</code>의 최신 커밋을 <code>base</code>로 <code>hotFix branch</code>의 커밋을 생성했기 때문에 <code>fast-forward</code> 병합이 발생</li>
<li>병합이 완료된 <code>hotFix branch</code>는 삭제함</li>
</ul>
<p><img src="https://images.velog.io/images/hwi_chance/post/715acdd8-2196-453f-b1dd-6bdadb3bf7f8/4.jpg" alt="3_way_merge"></p>
<ul>
<li>원래 작업을 수행 중이던 <code>feature branch</code>는 2번 커밋을 <code>base</code>로 작업 중이었기 때문에 4번 커밋을 병합해야 함</li>
<li><code>Conflict</code>이 발생할 수 있으며 <code>Confilct</code>을 해결한 병합 커밋이 추가로 생성됨</li>
</ul>
<h4 id="3-way-merge와-rebase-비교">3-Way Merge와 rebase 비교</h4>
<table>
<thead>
<tr>
<th align="center"></th>
<th align="center">3-Way Merge</th>
<th align="center">rebase</th>
</tr>
</thead>
<tbody><tr>
<td align="center">특징</td>
<td align="center">병합 커밋 생성</td>
<td align="center">현재 커밋들을 수정하면서 대상 브랜치 뒤에 재배치함</td>
</tr>
<tr>
<td align="center">장점</td>
<td align="center">한 번만 충돌 발생</td>
<td align="center">추가 커밋이 생성되지 않아 히스토리가 깔끔함</td>
</tr>
<tr>
<td align="center">단점</td>
<td align="center">추가 커밋이 생성됨</td>
<td align="center">여러 번 충돌이 발생할 수 있음</td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Git] Ch.5 Git 명령어]]></title>
            <link>https://velog.io/@hwi_chance/Git-Ch.5-Git-%EB%AA%85%EB%A0%B9%EC%96%B4</link>
            <guid>https://velog.io/@hwi_chance/Git-Ch.5-Git-%EB%AA%85%EB%A0%B9%EC%96%B4</guid>
            <pubDate>Fri, 12 Mar 2021 17:17:46 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>팀 개발을 위한 Git, GitHub 시작하기</strong>(정호영, 진유림 지음)으로 공부한 내용을 정리한 글입니다.</p>
</blockquote>
<h2 id="git-명령어">Git 명령어</h2>
<h3 id="init">init</h3>
<pre><code>$ git init</code></pre><ul>
<li>현재 폴더에 Git 저장소를 생성</li>
<li><code>.git</code> 폴더가 생성되며 이 폴더가 로컬저장소임</li>
</ul>
<blockquote>
<p><strong>Git 저장소</strong></p>
</blockquote>
<ul>
<li>Git 명령으로 관리할 수 있는 폴더 전체</li>
<li>공식 문서에서는 로컬저장소를 지칭</li>
</ul>
<blockquote>
<p><strong>로컬 저장소</strong>
커밋과 커밋을 구성하는 객체, 스테이지가 저장되는 폴더</p>
</blockquote>
<h3 id="status">status</h3>
<pre><code>$ git status</code></pre><ul>
<li><strong>Git 워킹트리</strong>의 상태를 보여주는 명령어</li>
<li>Git 워킹트리가 아닌 폴더에서 실행하면 오류 발생</li>
</ul>
<pre><code>$ git status -s</code></pre><ul>
<li>Git 워킹트리의 상태를 짧게 요약해서 보여주는 명령어</li>
<li>변경된 파일이 많을 때 유용</li>
</ul>
<blockquote>
<p><strong>Git working tree</strong></p>
</blockquote>
<ul>
<li>사용자가 파일과 하위 폴더를 만들고 작업 결과물을 저장하는 곳</li>
<li>워킹 디렉토리 혹은 작업 디렉토리라고도 함</li>
<li>정확하게는 작업 중인 폴더에서 <code>.git</code> 폴더를 뺀 나머지 부분을 지칭함</li>
</ul>
<h3 id="config">config</h3>
<pre><code>$ git config [--global | --local | --system] &lt;option&gt;</code></pre><ul>
<li>지정한 범위의 옵션 내용을 보여주는 명령어</li>
<li><code>--global</code>: 현재 사용자를 위한 옵션</li>
<li><code>--local</code>: 현재 Git 저장소에서만 유효한 옵션</li>
<li><code>--system</code>: PC 전체의 사용자를 위한 옵션<pre><code></code></pre></li>
</ul>
<p>$ git config [--global | --local | --system] <option> <value></p>
<pre><code>- 지정한 범위의 옵션 값을 새로 설정
</code></pre><p>$ git config [--global | --local | --system] --unset <option> <value></p>
<pre><code>- 지정한 범위의 옵션 값을 삭제
</code></pre><p>$ git config --list</p>
<pre><code>- 현재 프로젝트의 모든 옵션을 보여주는 명령어

### reset</code></pre><p>$ git reset <filename></p>
<pre><code>- `reset` 명령어는 커밋을 이전 상태로 돌리는 기능 이외에 스테이지 영역에 있는 파일을 `unstaging`하기도 함

### log</code></pre><p>$ git log</p>
<p>commit [commit id] (HEAD -&gt; master)
Author: [user_name] [user_email]
Date: Fri Jul 26 14:38:16</p>
<p>[commit message]</p>
<pre><code>- Git의 커밋 히스토리를 보여주는 명령어

&gt; **Commit ID**
- 40자리 16진수로 SHA1 해시 체크섬 값
- 전 세계에서 유일한 값이며 커밋 객체를 구별하는데 사용됨

&gt; **Commit message 7가지 규칙**
- 제목과 분문을 빈 줄로 분리
- 제목은 50자 이내로 
- 제목을 영어로 쓸 경우 첫 글자는 대문자로
- 제목에 마침표를 넣지 않음
- 제목을 영어로 쓸 경우 동사원형(현재형)으로 시작
- 본문을 72자 단위로 줄바꿈
- 어떻게 보다 무엇과 왜를 설명
</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[Git] Ch.4 실무 사례로 Git 다루기]]></title>
            <link>https://velog.io/@hwi_chance/Git-Ch.4-%EC%8B%A4%EB%AC%B4-%EC%82%AC%EB%A1%80%EB%A1%9C-Git-%EB%8B%A4%EB%A3%A8%EA%B8%B0</link>
            <guid>https://velog.io/@hwi_chance/Git-Ch.4-%EC%8B%A4%EB%AC%B4-%EC%82%AC%EB%A1%80%EB%A1%9C-Git-%EB%8B%A4%EB%A3%A8%EA%B8%B0</guid>
            <pubDate>Fri, 12 Mar 2021 07:53:27 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>팀 개발을 위한 Git, GitHub 시작하기</strong>(정호영, 진유림 지음)으로 공부한 내용을 정리한 글입니다.</p>
</blockquote>
<h2 id="amend">amend</h2>
<pre><code>$ git add new_file.txt
$ git commit --amend </code></pre><ul>
<li>커밋에 반영되었어야 할 내용이 누락된 경우 새로운 커밋을 생성할 필요없이, <code>amend</code> 명령어를 통해서 기존 커밋에 추가할 수 있음</li>
<li><code>amend</code> 명령어로 커밋 메시지도 수정할 수 있음</li>
<li>원격저장소에 이미 <code>push</code>된 커밋을 <code>amend</code>로 수정한 경우 원격저장소에 <code>force push</code>하여 변경된 커밋으로 덮어 씌워야 함</li>
</ul>
<h2 id="cherry-pick">cherry-pick</h2>
<pre><code>$ git cherry-pick [selected commit id]</code></pre><ul>
<li>다른 <code>branch</code>에 속한 커밋 중 원하는 하나만 현재 <code>branch</code>로 반영하는 기능</li>
<li>선택된 커밋 이전의 커밋이나 이후의 커밋은 현재 <code>branch</code>로 반영되지 않음</li>
</ul>
<h2 id="reset">reset</h2>
<pre><code>$ git reset --mixed [commit]
$ git reset --soft [commit]
$ git reset --hard [commit]</code></pre><ul>
<li><code>branch</code>를 옛날 커밋으로 되돌리는 기능</li>
<li><code>--mixed</code>: <code>reset</code>의 기본 옵션으로, 선택한 커밋 이후의 변경사항들은 없애지 않고 <code>unstaged</code> 상태(<code>untracked</code> 혹은 <code>modified</code>)로 남겨두는 옵션</li>
<li><code>--soft</code>: <code>--mixed</code>와 유사하지만 변경사항들을 <code>staged</code> 상태로 남겨두는 옵션</li>
<li><code>--hard</code>: 변경사항들을 모두 없애고 완전히 깔끔한 상태로 변경하는 옵션</li>
</ul>
<h2 id="revert">revert</h2>
<pre><code>$ git revert [commit]</code></pre><ul>
<li>잘못된 커밋을 생성한 경우 해당 커밋 이전으로 <code>reset</code>할 수도 있지만, 변경사항 되돌리는 커밋을 새로 생성하는 것이 더 좋음</li>
<li><code>revert</code>: 선택한 커밋 이전의 커밋 상태를 가지고 와서 새로운 커밋을 생성하는 명령어</li>
<li>즉, 잘못된 커밋을 제거하는 것이 아닌, 그 이전의 상태로 다시 덮어 씌우는 작업</li>
</ul>
<h2 id="stash">stash</h2>
<pre><code>$ git stash
$ git stash apply</code></pre><ul>
<li><code>stash</code>: 커밋하지 않은 변경 사항을 서랍에 넣어두기</li>
<li><code>stash apply</code> 서랍에 넣어둔 작업들을 다시 가져오기</li>
<li><code>tracked</code> 상태인 파일들만 서랍에 넣을 수 있음</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Git] Ch.3 둘 이상의 원격 저장소로 협업하기]]></title>
            <link>https://velog.io/@hwi_chance/Git-Ch.3-%EB%91%98-%EC%9D%B4%EC%83%81%EC%9D%98-%EC%9B%90%EA%B2%A9-%EC%A0%80%EC%9E%A5%EC%86%8C%EB%A1%9C-%ED%98%91%EC%97%85%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@hwi_chance/Git-Ch.3-%EB%91%98-%EC%9D%B4%EC%83%81%EC%9D%98-%EC%9B%90%EA%B2%A9-%EC%A0%80%EC%9E%A5%EC%86%8C%EB%A1%9C-%ED%98%91%EC%97%85%ED%95%98%EA%B8%B0</guid>
            <pubDate>Fri, 12 Mar 2021 06:20:00 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>팀 개발을 위한 Git, GitHub 시작하기</strong>(정호영, 진유림 지음)으로 공부한 내용을 정리한 글입니다.</p>
</blockquote>
<h2 id="원본저장소-복사">원본저장소 복사</h2>
<ul>
<li>기본적으로 원본저장소에 커밋을 직접 <code>push</code>하는 것은 그 저장소를 만든 사람만 할 수 있음</li>
<li>다른 사람이 <code>push</code>하기 위해선 저장소 소유자가 그 사람을 협력자로 등록해야 함</li>
<li>협력자가 늘어날 수록 원본저장소를 관리하기가 어려워짐 <h3 id="fork">fork</h3>
</li>
<li>협력자를 추가하는 대신, 원본저장소를 <code>fork</code>로 다른 원격저장소에 <strong>복사</strong>하고 추가로 코드를 작성한 뒤, <code>pull request</code>를 통해 원본저장소에 반영하는 방식을 사용</li>
<li><code>fork</code>시 <code>branch</code>를 포함한 원본 저장소의 모든 커밋을 복사함 </li>
<li><code>branch</code>를 통해 코드 분기점을 만들고 <code>pull request</code>를 통해 병합하는 과정과 유사하지만 <code>fork</code>한 원격저장소는 <code>branch</code>와는 달리 원본 저장소로부터 독립되어 동작함</li>
<li><code>fork</code>한 원격저장소는 <code>clone</code>으로 로컬저장소에 내려받은 뒤 작업을 함</li>
</ul>
<h2 id="원본저장소에-pull-request">원본저장소에 Pull Request</h2>
<ul>
<li><code>fork</code>한 원격저장소의 한 <code>branch</code>를 원본저장소의 한 <code>branch</code>에 병합하는 작업</li>
<li>오픈소스에 <code>pull reqeust</code>를 보내기 전에 <code>Contribution Guideline</code>이 있는지 확인하는 것이 좋음</li>
<li><code>pull request</code>가 받아들여지면 <code>fork</code>한 사용자가 원본저장소에 <code>Contributor</code>로 등록됨</li>
</ul>
<h2 id="commit-이력-조작하기">Commit 이력 조작하기</h2>
<ul>
<li><code>fork</code>한 원격저장소는 <code>fork</code>한 시점의 원본저장소 커밋 이력을 복사함</li>
<li>이후 원본저장소에 새로운 커밋이 생겨도 <code>fork</code>한 원격저장소는 해당 커밋에 대해 알지 못 함</li>
<li>이러한 상황에서 <code>pull request</code>를 한다면 <code>Conflict</code>이 발생할 수 있음</li>
<li>병합시 <code>Conflict</code>을 해결한 커밋이 추가로 생성됨 <h3 id="여러-원격저장소-추적">여러 원격저장소 추적</h3>
</li>
<li><code>fork</code>한 원격저장소를 내려받은 로컬저장소는 <code>fork</code>한 원격저장소를 <code>origin</code>이라는 이름을 붙이고 추적함 </li>
<li>로컬저장소에서 원본저장소의 히스토리를 보기 위해선 원본저장소도 추적해야 함</li>
<li><code>$ git add remote upstream [원본저장소 주소]</code>: 원본저장소는 관용적으로 <code>upstream</code>이라는 이름을 붙임<h3 id="fetch">fetch</h3>
<img src="https://images.velog.io/images/hwi_chance/post/2e545e81-f192-41c3-8978-e4125a0d85ec/fetch_pull.jpg" alt="fetch_pull"></li>
<li>원격저장소의 커밋 이력을 가져와 로컬저장소의 커밋 이력을 업데이트하는 기능</li>
<li>커밋 이력을 가져와 최신 코드를 내 코드에 반영하는 <code>pull</code>과는 달리 커밋 이력만 가져옴<h3 id="rebase">rebase</h3>
<img src="https://images.velog.io/images/hwi_chance/post/40af8ba4-5bdd-4c66-99df-483bd2834fdc/rebase.jpg" alt="rebase"></li>
<li>커밋의 <code>base commit</code>을 바꾸는 기능</li>
<li>위 예시에서 1, 2번 커밋의 <code>base</code>를 0번 커밋에서 4번 커밋으로 바꾸는 과정
1) 1번 커밋과 4번 커밋을 비교하여 충돌을 확인하고 해결
2) 충돌을 해결한 1번 커밋을 4번 커밋과 연결
3) 1&#39;번 커밋과 2번 커밋을 비교하여 충돌을 확인하고 해결
4) 충돌을 해결한 2번 커밋을 1&#39;번 커밋과 연결</li>
<li>커밋 히스토리를 바꾸는 것이기 때문에 히스토리가 꼬이지 않도록 혼자 쓰는 <code>branch</code>에서 <code>rebase</code>해야 함  </li>
<li><code>rebase</code>한 커밋 이력을 원격저장소에 반영할 경우 <code>force push</code>로 강제 푸쉬해야 함</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Git] Ch.2 Git으로 협업하기]]></title>
            <link>https://velog.io/@hwi_chance/Git-Ch.2-Git%EC%9C%BC%EB%A1%9C-%ED%98%91%EC%97%85%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@hwi_chance/Git-Ch.2-Git%EC%9C%BC%EB%A1%9C-%ED%98%91%EC%97%85%ED%95%98%EA%B8%B0</guid>
            <pubDate>Thu, 11 Mar 2021 17:08:13 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>팀 개발을 위한 Git, GitHub 시작하기</strong>(정호영, 진유림 지음)으로 공부한 내용을 정리한 글입니다.</p>
</blockquote>
<h2 id="branch란">Branch란?</h2>
<h3 id="branch-생성-이유">Branch 생성 이유</h3>
<p><img src="https://images.velog.io/images/hwi_chance/post/cd37df26-7f6f-40a6-ad02-e5349f76637b/no%20branch.jpg" alt="no_branch"></p>
<ul>
<li><code>branch</code> 없이 커밋 한다면 기본 <code>branch</code>인 <code>master</code>의 커밋이 됨</li>
<li><code>master</code> 커밋은 한 줄로, 시간 순으로 쌓이며 각 커밋들은 이전 커밋을 가리키고 있음</li>
</ul>
<p><img src="https://images.velog.io/images/hwi_chance/post/ced3300d-a485-4817-bf50-8fd968587e6c/branch.jpg" alt="branch"></p>
<ul>
<li>한 커밋을 기준으로 여러 사람이 각각 새로운 버전을 생성한 경우 생성된 버전들은 이전 커밋을 가리켜야 하기 때문에 한 줄로 연결될 수 없음 </li>
<li><code>branch</code>라는 새로운 작업 줄기를 생성하여 병렬적으로 연결해야 함</li>
</ul>
<h3 id="branch는-포인터">Branch는 포인터</h3>
<ul>
<li><code>branch</code>는 일종의 포인터로 해당 <code>branch</code>에서 생성한 커밋 중 가장 최신 커밋을 가리킴</li>
<li>분기를 만들 때 프로젝트를 통째로 복사해야 하는 SVN 같은 버전 관리 툴과는 달리 Git은 포인터로 분기를 만들기 때문에 가볍고 빠름</li>
<li>복사된 커밋이 아닌 동일한 커밋을 다른 <code>branch</code>가 가리킬 수도 있음</li>
</ul>
<h3 id="head">HEAD</h3>
<ul>
<li>많고 여러 갈래로 나뉜 커밋 중에서 현재 어디에 위치하고 있는지 가리키는 포인터</li>
<li>예를 들어 <code>git checkout</code>을 통해 Master Commit 2로 이동했다면 <code>HEAD</code>는 Master Commit 2를 가리키고 있음</li>
<li><code>HEAD</code>가 <code>master branch</code>의 포인터와는 다른 커밋을 가리킨다면 <code>HEAD</code>는 <code>Detached HEAD</code> 상태가 됨</li>
</ul>
<h2 id="branch-생성-및-이동">Branch 생성 및 이동</h2>
<h3 id="feature-branch">feature branch</h3>
<ul>
<li>일반적으로 <code>master branch</code>에는 직접 커밋하지 않음</li>
<li>일반적으로 기능 개발을 위한 <code>branch</code>는 <code>feature/[기능 이름]</code>으로 생성함</li>
<li><code>feature branch</code>에서 기능 개발이 끝나면 <code>master branch</code>에 합침</li>
<li>병합되어 더 이상 필요하지 않은 <code>feature branch</code>는 삭제</li>
</ul>
<h3 id="checkout">checkout</h3>
<ul>
<li><code>branch</code>를 생성할 때 기준(base)이 될 커밋으로 이동하기 위한 기능</li>
</ul>
<h2 id="branch-합치기">Branch 합치기</h2>
<p>Branch 합치기(<code>merge</code>)는 두 버전(커밋)의 합집합을 구하고 하나의 <code>branch</code>
에 속하도록 만드는 것을 말함</p>
<h3 id="fast-forward">Fast-forward</h3>
<ul>
<li>두 커밋의 합집합이 둘 중 하나와 동일한 경우</li>
<li>새로운 커밋 생성 없이 합집합과 동일한 커밋을 <code>branch</code>가 가리키도록 함</li>
</ul>
<h3 id="conflict">Conflict</h3>
<ul>
<li>두 커밋이 동일한 부분을 서로 다르게 수정한 경우 <code>Conflict</code>이 발생함</li>
<li><code>Conflict</code>이 발생할 경우를 대비해 일반적으로 먼저 <code>feature branch</code>로 <code>master branch</code>의 커밋을 병합함</li>
<li><code>feature branch</code>로 병합했을 때 <code>Conflict</code>이 발생하면 <code>Conflict</code>을 해결하고 커밋 한 뒤, 해당 커밋을 <code>master branch</code>와 병합함</li>
<li><code>Conflict</code>를 해결한 커밋은 <code>Fast-forward</code>로 <code>master branch</code>와 합쳐짐</li>
</ul>
<h3 id="merge-commit">Merge Commit</h3>
<ul>
<li><code>Fast-forward</code>도 아니고 <code>Conflict</code>도 발생하지 않는 경우 두 커밋을 합집합한 새로운 <code>Merge Commit</code>이 생성됨</li>
</ul>
<h2 id="pull-request">Pull Request</h2>
<ul>
<li>협력자에게 <code>branch</code> 병합을 요청하는 메시지를 보내는 GitHub의 기능</li>
<li><code>base branch</code>로 <code>compare branch</code>의 커밋이 병합되는 형태</li>
<li>코드의 라인마다 댓글을 달 수 있어서 해당 코드가 왜 고쳐졌는지, 어떻게 개선할 수 있는지 토론할 수 있음</li>
<li><code>pull request</code>가 승낙되면 GitHub를 통해 병합이 진행되므로 병합 후 내 로컬 저장소로 <code>pull</code>하는 과정이 필요함</li>
</ul>
<h2 id="프로젝트-release">프로젝트 Release</h2>
<h3 id="프로그램-version">프로그램 Version</h3>
<ul>
<li>프로그램 개발 과정에서 의미있는 특정 시점들을 버전으로 명시함 </li>
<li>Major upgrade: 사용자들이 크게 변화를 느끼도록 프로그램을 바꿨을 경우 메이저 버전을 올림 (<code>1.2.6 -&gt; 2.0.0</code>)</li>
<li>Minor upgrade: 작은 변화가 생겼을 경우 마이너 버전을 올림 (<code>1.2.6 -&gt; 1.3.0</code>)</li>
<li>Maintenance upgrade: 버그나 유지보수 등 작은 수정을 했을 경우 메인터넌스 버전을 올림 (<code>1.2.6 -&gt; 1.2.7</code>)<h3 id="tag">Tag</h3>
</li>
<li><code>Release</code>: 프로그램을 사용자들이 쓸 수 있도록 배포하는 것</li>
<li><code>Tag</code>: 코드 버전 등 커밋에 간단한 태그를 붙일 때 사용하는 기능</li>
<li>태그도 일종의 포인터여서 해당 태그가 붙은 커밋을 가리키고 있음</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Git] Ch.1 혼자서 Git으로 버전 관리]]></title>
            <link>https://velog.io/@hwi_chance/Git-Ch.1-%ED%98%BC%EC%9E%90%EC%84%9C-Git%EC%9C%BC%EB%A1%9C-%EB%B2%84%EC%A0%84-%EA%B4%80%EB%A6%AC</link>
            <guid>https://velog.io/@hwi_chance/Git-Ch.1-%ED%98%BC%EC%9E%90%EC%84%9C-Git%EC%9C%BC%EB%A1%9C-%EB%B2%84%EC%A0%84-%EA%B4%80%EB%A6%AC</guid>
            <pubDate>Thu, 11 Mar 2021 12:46:54 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>팀 개발을 위한 Git, GitHub 시작하기</strong>(정호영, 진유림 지음)으로 공부한 내용을 정리한 글입니다.</p>
</blockquote>
<h2 id="git과-다른-버전-관리-시스템의-차이">Git과 다른 버전 관리 시스템의 차이</h2>
<h3 id="다른-버전-관리-시스템">다른 버전 관리 시스템</h3>
<pre><code>  Prev commit              Next commit

---README.txt---         ---README.txt---
| 1. 가나다     |         |              |
| 2. 라마바     |    -    |              |
|              |         | 3. 사아자     |
----------------         ----------------</code></pre><ul>
<li>SVN(SubVersion)과 같은 버전 관리 시스템은 커밋에 변경사항만 부분적으로 저장함</li>
<li>각 버전의 소스 코드를 볼 때 맨 처음 커밋부터 거슬러 올라가 바뀐 점을 모두 반영하는 작업을 해야함</li>
</ul>
<h3 id="git">Git</h3>
<pre><code>  Prev commit              Next commit

---README.txt---         ---README.txt---
| 1. 가나다     |         | 1. 가나다     |
| 2. 라마바     |    -    | 2. 라마바     |
|              |         | 3. 사아자     |
----------------         ----------------</code></pre><ul>
<li><strong>Git</strong>은 커밋에 변경된 파일을 통째로 저장함 (<strong>Snapshot</strong>)</li>
<li>각 버전의 소스 코드를 볼 때 바로 앞 커밋이랑만 비교하면 됨</li>
<li>바뀌지 않은 파일은 이전 커밋의 파일 링크만 저장</li>
</ul>
<h2 id="git으로-관리하는-파일-상태">Git으로 관리하는 파일 상태</h2>
<h3 id="untracked">untracked</h3>
<ul>
<li><code>untracked</code>: 한 번도 커밋된 적이 없는 파일의 상태<h3 id="tracked">tracked</h3>
</li>
<li><code>staged</code>: <code>add</code> 명령어를 통해 <code>stage</code>에 올라온 파일의 상태</li>
<li><code>unmodified</code>: <code>stage</code>에 속한 파일들은 <code>commit</code> 명령어 실행시 해당 버전에 저장되고 <code>unmodified</code> 상태가 됨</li>
<li><code>modified</code>: 커밋된 적이 있고 마지막 커밋 이후로 내용이 변경된 파일의 상태</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Git] Ch.0 Git과 GitHub 기본]]></title>
            <link>https://velog.io/@hwi_chance/Git-Ch.0-Git%EA%B3%BC-GitHub-%EA%B8%B0%EB%B3%B8</link>
            <guid>https://velog.io/@hwi_chance/Git-Ch.0-Git%EA%B3%BC-GitHub-%EA%B8%B0%EB%B3%B8</guid>
            <pubDate>Thu, 11 Mar 2021 11:10:13 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>팀 개발을 위한 Git, GitHub 시작하기</strong>(정호영, 진유림 지음)으로 공부한 내용을 정리한 글입니다.</p>
</blockquote>
<h2 id="git과-github">Git과 GitHub</h2>
<h3 id="git"><strong>Git</strong></h3>
<p>소스 코드 데이터를 저장하고 내가 원하는 시점의 소스 코드 데이터로 이동할 수 있게 해주는 <strong>버전 관리 툴</strong></p>
<h3 id="github">GitHub</h3>
<p>Git으로 관리하는 프로젝트를 올려둘 수 있는 <a href="https://github.com/">Git 호스팅 사이트</a> 중 하나. 다른 호스팅 사이트로는 <a href="https://about.gitlab.com/">GitLab</a>과 <a href="https://bitbucket.org/">BitBucket</a> 등이 있음</p>
<h3 id="오픈소스">오픈소스</h3>
<p>누구든지 볼 수 있고 기여할 수 있는 <strong>공개 프로젝트</strong>. Git은 오픈소스 활동을 전 세계로 확장시킨 일등공신</p>
<h2 id="git-기본-사용">Git 기본 사용</h2>
<h3 id="initialize">Initialize</h3>
<h4 id="git-로컬-저장소-만들기">Git 로컬 저장소 만들기</h4>
<pre><code>$ git init</code></pre><ul>
<li>내 컴퓨터에 존재하는 폴더를 Git과 연결하는 <strong>CLI</strong></li>
<li>명령어를 실행하고 나면 <code>.git</code> 폴더가 생성되는데, 이 폴더에는 Git으로 생성한 <strong>버전들의 정보</strong>와 <strong>원격저장소 주소</strong> 등이 들어 있음</li>
<li>일반적으로 <code>.git</code> 폴더를 로컬저장소라고 부름</li>
</ul>
<blockquote>
<p><strong>CLI</strong>
Command-Line Interface. 가상 터미널 또는 텍스트 터미널을 통해 사용자와 컴퓨터가 상호 작용하는 방식 (출처: <a href="https://ko.wikipedia.org/wiki/%EB%AA%85%EB%A0%B9_%EC%A4%84_%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4">위키백과</a>)</p>
</blockquote>
<h4 id="git에-내-정보-등록">Git에 내 정보 등록</h4>
<pre><code>$ git config --global user.name &quot;user name&quot;

$ git config --global user.email &quot;user email&quot;</code></pre><ul>
<li>버전 생성자 확인을 위해 정보 등록</li>
<li>연동할 <strong>Git 호스팅 사이트</strong> 계정 정보와 동일하게 입력하는 것이 좋음</li>
</ul>
<h3 id="commit">Commit</h3>
<h4 id="commit-파일-추가">Commit 파일 추가</h4>
<pre><code>$ git add [file name]</code></pre><ul>
<li>특정 파일 추가</li>
</ul>
<pre><code>$ git add .</code></pre><ul>
<li>모든 파일 추가</li>
</ul>
<h4 id="commit-하기">Commit 하기</h4>
<pre><code>$ git commit -m &quot;commit message&quot;</code></pre><ul>
<li>Git에 새 버전 생성 </li>
</ul>
<h4 id="지금까지-commit한-내역-확인">지금까지 Commit한 내역 확인</h4>
<pre><code>$ git log

commit [commit id] (HEAD -&gt; master)
Author: [user_name] [user_email]
Date: Fri Jul 26 14:38:16

[commit message]

commit [commit id]
Author: [user_name] [user_email]
Date: Fri Jul 26 14:34:43

[commit message]</code></pre><ul>
<li>최신 커밋부터 오래된 커밋 순으로 보여줌</li>
</ul>
<h4 id="특정-commit으로-코드-되돌리기">특정 Commit으로 코드 되돌리기</h4>
<pre><code>$ git checkout [commit id]</code></pre><ul>
<li><code>commit id</code>는 앞 7자리까지만 써도 됨</li>
</ul>
<h4 id="최신-commit으로-코드-되돌리기">최신 Commit으로 코드 되돌리기</h4>
<pre><code>$ git checkout -</code></pre><ul>
<li><code>-</code>는 최신 커밋을 의미</li>
</ul>
<h2 id="github-기본-사용법">GitHub 기본 사용법</h2>
<h3 id="github에-commit-올리기">GitHub에 Commit 올리기</h3>
<h4 id="원격저장소-주소-등록">원격저장소 주소 등록</h4>
<pre><code>$ git remote add origin [address]</code></pre><ul>
<li><strong>GitHub</strong>에 Repository를 생성하고 그 주소를 Git에 등록</li>
<li><code>origin</code>: 원격저장소의 닉네임</li>
</ul>
<h4 id="github에-commit-push">GitHub에 Commit Push</h4>
<pre><code>$ git push origin [branch name]</code></pre><ul>
<li>등록된 원격저장소에 커밋 내용이 업로드 됨</li>
<li><code>branch name</code>: 커밋을 올리는 줄기 이름</li>
<li><code>master</code>: 기본 branch 이름. 따로 줄기를 생성하지 않으면 Git은 <code>master</code>에 커밋을 올림</li>
</ul>
<h3 id="github에서-commit-내려받기">GitHub에서 Commit 내려받기</h3>
<h4 id="clone">Clone</h4>
<pre><code>$ git clone [repository address] .</code></pre><ul>
<li>원격저장소의 주소와 코드, 그리고 버전 전체를 내 컴퓨터로 내려받음</li>
<li><code>.</code>은 현재 폴더에 내려받겠다는 뜻</li>
<li><code>.</code>이 없으면 <code>repository</code> 제목으로 폴더가 생성되고 그곳에 내려받아짐</li>
<li>GitHub에서 <code>[Download ZIP]</code>을 통해 원격저장소나 버전들은 제외하고 파일들만을 내려받을 수도 있음</li>
</ul>
<h4 id="pull">Pull</h4>
<pre><code>$ git pull origin master</code></pre><ul>
<li>원격저장소에 새로운 커밋들이 있다면 해당 커밋들은 내 로컬저장소로 내려받음</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Android] 안드로이드 10(Android Q) Scoped Storage]]></title>
            <link>https://velog.io/@hwi_chance/Android-%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-10-Android-Q-Scoped-Storage</link>
            <guid>https://velog.io/@hwi_chance/Android-%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-10-Android-Q-Scoped-Storage</guid>
            <pubDate>Tue, 09 Mar 2021 10:42:40 GMT</pubDate>
            <description><![CDATA[<h3 id="scoped-storage">Scoped Storage</h3>
<p>안드로이드의 저장 공간은 크게 내부 저장소와 외부 저장소로 구분됩니다. <strong>Scoped Storage</strong>는 외부 저장소에 적용되는 개념으로, <strong>안드로이드 10(Android Q)</strong> 버전에서 보안 강화를 목적으로 등장했습니다. <strong>Scoped Storage</strong>의 등장으로 외부 저장소 관리는 Android Q 이전과는 완전히 달라지게 되었습니다. </p>
<h3 id="android-q-이전의-저장-공간">Android Q 이전의 저장 공간</h3>
<p><img src="https://images.velog.io/images/hwi_chance/post/4f8f2dbe-9715-4a9e-9549-a7aa45f0a0f3/Q_before.jpg" alt="Android_Q_before"></p>
<p>안드로이드의 <strong>내부 저장소</strong>는 시스템과 기기에 설치된 앱들이 사용하는 공간입니다. 각 앱들은 개별적인 공간을 할당 받으며, 이 곳에 앱 동작에 필요한 파일들을 저장합니다. 또 각자의 공간은 권한 없이 접근할 수 있으나, 다른 앱의 공간엔 아예 접근할 수 없습니다. 내부 저장소는 샌드박스 구조로 되어 있기 때문에 시스템에 의해 안전하게 보호됩니다. </p>
<blockquote>
<p><strong>샌드박스(sandbox)</strong> 
외부로부터 들어온 프로그램이 보호된 영역에서 동작해 시스템이 부정하게 조작되는 것을 막는 보안 형태 (출처: <a href="https://ko.wikipedia.org/wiki/%EC%83%8C%EB%93%9C%EB%B0%95%EC%8A%A4_(%EC%BB%B4%ED%93%A8%ED%84%B0_%EB%B3%B4%EC%95%88)">위키피디아</a>)</p>
</blockquote>
<p><strong>외부 저장소</strong>는 모든 앱들이 공유하는 하나의 공용 저장소로, 외부 저장소 접근 권한이 있다면 외부 저장소 내의 <strong>모든 파일에 접근 가능</strong>합니다. 외부 저장소 내에도 내부 저장소처럼 각 앱들을 위한 개별적인 공간이 존재하며, 이 공간 역시 권한이 있다면 마음껏 접근할 수 있습니다. 개별 공간이 공유 공간과 다른점은 해당 공간을 소유한 앱은 권한이 없어도 해당 공간에 접근할 수 있고 해당 앱이 삭제되면 할당되었던 공간이 해제된다는 점입니다. </p>
<p>이러한 특성을 가지고 있는 외부 저장소는 다른 앱의 파일에 쉽게 접근할 수 있다는 점에서 보안적으로 취약하다고 볼 수 있습니다.</p>
<h3 id="android-q-이후의-저장-공간">Android Q 이후의 저장 공간</h3>
<p><img src="https://images.velog.io/images/hwi_chance/post/ed0407ee-3400-4b77-a67b-c3030ecfc06c/Q_after.jpg" alt="Android_Q_after"></p>
<p><strong>Scoped Storage</strong>가 적용된 Android Q 이후의 외부 저장소는 Android Q 이전의 외부 저장소와 개념적으로 완전히 달라지게 됩니다. </p>
<p>먼저, <strong>개별 공간</strong>은 샌드박스 구조로 바뀌었습니다. 해당 공간을 소유한 앱은 권한 없이 해당 공간에 접근할 수 있으나 그렇지 않은 앱들은 해당 공간에 직접적으로 접근할 수 없습니다. </p>
<p><strong>공용 공간</strong>은 개별 공간의 파일들을 개념적으로 모아놓은 영역으로 <code>사진 및 동영상</code> / <code>음악</code> / <code>다운로드</code>로 영역이 구분되어 있습니다. 공용 공간에는 타입에 맞는 파일만 저장할 수 있으며(다운로드는 제한 없음) <code>MediaStore</code>를 통해서만 접근할 수 있습니다. 각 영역에 파일을 생성하거나 자신이 생성한 파일을 읽을 땐 추가 권한이 필요하지 않습니다. 그러나 다른 앱이 생성한 파일을 읽기 위해선 여전히 권한이 필요하고 해당 파일에 쓰는 것은 쓰기 권한이 없어졌기 때문에 불가능합니다. 앱이 삭제되도 해당 앱이 생성(소유)한 공용 공간의 파일은 삭제되지 않습니다.</p>
<h3 id="reference">Reference</h3>
<ul>
<li><a href="https://brunch.co.kr/@huewu/8">안드로이드 Q Scoped Storage 이해하기</a></li>
<li><a href="https://www.youtube.com/watch?v=UnJ3amzJM94">Preparing for scoped storage</a></li>
<li><a href="https://www.youtube.com/watch?v=RjyYCUW-9tY">Android 11의 현대식 저장소</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Android] 안드로이드 View의 위치와 크기]]></title>
            <link>https://velog.io/@hwi_chance/Android-%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-View%EC%9D%98-%EC%9C%84%EC%B9%98%EC%99%80-%ED%81%AC%EA%B8%B0</link>
            <guid>https://velog.io/@hwi_chance/Android-%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-View%EC%9D%98-%EC%9C%84%EC%B9%98%EC%99%80-%ED%81%AC%EA%B8%B0</guid>
            <pubDate>Tue, 16 Feb 2021 13:20:13 GMT</pubDate>
            <description><![CDATA[<h2 id="view-position">View Position</h2>
<p><img src="https://images.velog.io/images/hwi_chance/post/6face51b-024b-4fc0-948c-af57a807e789/view%20position.jpg" alt="View Position"></p>
<h4 id="안드로이드-좌표계">안드로이드 좌표계</h4>
<p>안드로이드는 화면의 좌측 상단을 (0, 0)으로 하고 x좌표는 오른쪽으로 갈수록 증가하며 y좌표는 아래쪽으로 갈수록 증가하는 좌표계를 갖습니다. 상태바는 윈도우에 오버레이 되어 있는 것이기 때문에 상태바의 좌측 상단이 (0, 0)입니다. </p>
<h4 id="view의-위치와-크기-표현">View의 위치와 크기 표현</h4>
<p><code>View</code>는 실제 모양과 달리 직사각형의 형상을 가집니다. <code>View</code>는 <code>left</code>와 <code>top</code>의 쌍으로 위치를 표현하고, <code>width</code>와 <code>height</code>로 2차원의 크기를 표현합니다. 각가의 단위는 픽셀입니다. </p>
<h4 id="메소드">메소드</h4>
<ul>
<li><code>getLeft()</code>와 <code>getTop()</code>: <code>View</code>를 표현하는 직사각형의 좌측 상단의 X, Y좌표를 반환하며 반환된 좌표는 <code>View</code>의 부모를 기준으로한 상대 좌표입니다.</li>
<li><code>getRight()</code>: <code>left + width</code>를 반환합니다.</li>
<li><code>getBottom()</code>: <code>Top + height</code>를 반환합니다.</li>
</ul>
<h2 id="view-size">View Size</h2>
<p><code>View</code>의 크기는 <code>width</code>와 <code>height</code>의 쌍으로 표현됩니다. 실제로 <code>View</code>는 두 가지의 <code>width</code>, <code>height</code> 쌍을 가집니다. </p>
<ul>
<li><code>measured width</code>와 <code>measured height</code>: <code>View</code>의 부모 내에서 <code>View</code>가 어느정도의 크기를 갖는지 정의하는 값입니다. </li>
<li><code>width</code>와 <code>height</code>: <code>drawing width</code>, <code>drawing height</code>라고도 하며, 레이아웃이 그려지고 난 뒤 화면에 그려질 <code>View</code>의 실제 크기를 정의하는 값입니다. <code>measured width</code>, <code>measured height</code>와는 다른 값을 가질 수도 있습니다.</li>
</ul>
<h4 id="메소드-1">메소드</h4>
<ul>
<li><code>getMeasuredWidth()</code>와 <code>getMeasuredHeight()</code>: <code>measured width</code>와 <code>measured height</code> 값을 반환합니다. </li>
<li><code>getWidth()</code>와 <code>getHeight()</code>: <code>width</code>와 <code>height</code> 값을 반환합니다.</li>
</ul>
<h2 id="layout">Layout</h2>
<p><code>Layout</code>은 측정 단계와 레이아웃 단계로 이루어집니다. </p>
<h4 id="측정-단계">측정 단계</h4>
<p>측정 단계는 <code>measure(widthMeasureSpec, heightMeasureSpec)</code> 메소드로 발생하며, <code>View tree</code>를 <code>top-down</code> 방식으로 순회하면서 동작합니다. 측정값은 각 <code>View</code>에 저장됩니다. 측정된 너비와 높이는 부모의 제약 조건을 만족해야 합니다. 이를 통해 측정 단계가 끝나면 모든 부모가 그들의 자식들의 측정값을 수용하는 것을 보장합니다.</p>
<h4 id="레이아웃-단계">레이아웃 단계</h4>
<p>레이아웃 단계는 <code>layout(l, t, r, b)</code> 메소드로 발생하며, 역시 <code>View tree</code>를 <code>top-down</code> 방식으로 순회하며 동작합니다. 이 단계를 진행하면서 각 부모들은 측정 단계에서 계산된 사이즈를 이용하여 자식들의 위치를 결정해야 합니다. </p>
<h2 id="translation">translation</h2>
<p><img src="https://images.velog.io/images/hwi_chance/post/278b1582-039e-424e-a2c1-b55cf3568cc1/xy.png" alt="xy">
<code>View</code>를 <code>setTranslationX()</code>, <code>setTranslationY()</code> 메소드를 통해서 이동시킬 수 있다. 이 이동은 <code>View</code>가 레이아웃에 배치되고 난 후에 이동시키고 싶을 때 사용하는 이동입니다. </p>
<h3 id="메소드-2">메소드</h3>
<ul>
<li><code>getX()</code>: <code>left + translationX</code>의 값을 반환합니다. 이는 <code>translation</code> 후의 X 좌표를 나타냅니다.</li>
<li><code>getY()</code>: <code>top + translationY</code>의 값을 반환합니다. 이는 <code>translation</code> 후의 Y 좌표를 나타냅니다.</li>
</ul>
<h2 id="reference">Reference</h2>
<ol>
<li><a href="https://developer.android.com/reference/kotlin/android/view/View">Android Developers - View</a></li>
<li><a href="https://stackoverflow.com/questions/30202379/android-views-gettop-getleft-getx-gety-getwidth-getheight-meth">Stackoverflow</a></li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Kotlin] Scope Functions]]></title>
            <link>https://velog.io/@hwi_chance/Kotlin-Scope-Functions</link>
            <guid>https://velog.io/@hwi_chance/Kotlin-Scope-Functions</guid>
            <pubDate>Tue, 02 Feb 2021 12:58:49 GMT</pubDate>
            <description><![CDATA[<h1 id="scope-functions">Scope Functions</h1>
<p><strong>Scope Functions</strong>은 객체의 컨텍스트 내에서 코드 블록을 실행할 수 있게 하는 함수입니다. <code>lambda expression</code>이 제공된 객체에서 <code>Scope Function</code>을 호출하면 해당 함수는 일시적인 범위를 형성하며, 이 범위에서는 객체 이름 없이 객체에 접근할 수 있습니다. </p>
<p><code>Scope Function</code>의 일반적인 형태는 다음과 같습니다.</p>
<pre><code class="language-kotlin">Person(&quot;Alice&quot;, 20, &quot;Amsterdam&quot;).let {
    println(it)
    it.moveTo(&quot;London&quot;)
    it.incrementAge()
    println(it)
}</code></pre>
<p>만약 <code>Scope Function</code>을 사용하지 않는다면 위의 코드는 아래와 같이 작성되어야 합니다.</p>
<pre><code class="language-kotlin">val alice = Person(&quot;Alice&quot;, 20, &quot;Amsterdam&quot;)
println(alice)
alice.moveTo(&quot;London&quot;)
alice.incrementAge()
println(alice)</code></pre>
<p><code>Scope Function</code>은 새로운 기술 기능을 도입하지는 않지만 코드를 보다 간결하고 읽기 쉽게 만들 수 있습니다.</p>
<p>Kotlin에서 제공하는 <code>Scope Function</code>으로는 <code>let</code>, <code>run</code>, <code>with</code>, <code>apply</code> 그리고 <code>also</code>가 존재합니다. 기본적으로 이 함수들은 객체의 코드 블럭을 실행한다는 공통점을 갖습니다. 차이점은 코드 블럭 내에서 객체에 어떻게 접근하는지와 블럭 내 코드의 결과가 무엇이냐입니다. </p>
<h3 id="context-object-this-or-it">Context object: <code>this</code> or <code>it</code></h3>
<p><code>Scope Function</code>의 <code>lambda expression</code> 내부에서 컨텍스트 객체는 실제 이름 대신 짧은 참조를 통해 사용가능합니다. 이 짧은 참조는 <code>lambda receiver</code>인 <code>this</code> 혹은 <code>lambda argument</code>인 <code>it</code>입니다. </p>
<pre><code class="language-kotlin">fun main() {
    val str = &quot;Hello&quot;
    // this
    str.run {
        println(&quot;The receiver string length: $length&quot;)
        //println(&quot;The receiver string length: ${this.length}&quot;) // does the same
    }

    // it
    str.let {
        println(&quot;The receiver string&#39;s length is ${it.length}&quot;)
    }
}</code></pre>
<h4 id="this"><code>this</code></h4>
<p><code>run</code>, <code>with</code> 그리고 <code>apply</code>는 컨텍스트 객체를 <code>this</code>를 통해 사용합니다. 그래서 객체를 일반적인 클래스 함수 내부에서 사용하는 것처럼 사용할 수 있습니다. 대부분의 경우 객체 멤버에 할당을 할 때 <code>this</code>를 생략할 수 있습니다. 다만 외부 객체나 함수와 구분하기 힘들어지므로 <code>this</code>를 사용하는 것을 권장합니다.</p>
<pre><code class="language-kotlin">val adam = Person(&quot;Adam&quot;).apply { 
    age = 20                       // same as this.age = 20 or adam.age = 20
    city = &quot;London&quot;
}</code></pre>
<h4 id="it"><code>it</code></h4>
<p><code>let</code>과 <code>also</code>는 컨텍스트 객체를 <code>lambda argument</code>처럼 가지고 있습니다. 따라서 <code>it</code>을 통해 컨텍스트 객체에 접근할 수 있습니다. <code>it</code>은 <code>this</code>보다 짧고 <code>it</code>을 사용한 표현식은 보통 읽기 쉽습니다. 하지만 객체의 함수나 프로퍼티를 호출할 때 <code>this</code>처럼 암시적으로 사용할 수는 없습니다. </p>
<pre><code class="language-kotlin">fun getRandomInt(): Int {
    return Random.nextInt(100).also {
        writeToLog(&quot;getRandomInt() generated value $it&quot;)
    }
}</code></pre>
<p>추가로 컨텍스트 객체를 <code>argument</code>로 전달한다면 컨텍스트 객체에 이름을 부여할 수 있습니다.</p>
<pre><code class="language-kotlin">fun getRandomInt(): Int {
    return Random.nextInt(100).also { value -&gt;
        writeToLog(&quot;getRandomInt() generated value $value&quot;)
    }
}</code></pre>
<h3 id="return-value">Return value</h3>
<p><code>Scope Function</code>은 코드 블럭이 반환하는 결과에서 차이를 보입니다. 따라서 <code>Scope Function</code>을 실행한 뒤 어떤 작업을 하냐에 따라 사용할 <code>Scope Function</code>을 결정합니다. </p>
<h4 id="context-object">Context object</h4>
<p><code>apply</code>와 <code>also</code>는 해당 코드 블럭의 컨텍스트 객체를 반환합니다. 따라서 이 두 함수는 같은 객체에 대한 <code>call chain</code>을 가질 수 있습니다. </p>
<pre><code class="language-kotlin">val numberList = mutableListOf&lt;Double&gt;()
numberList.also { println(&quot;Populating the list&quot;) }
    .apply {
        add(2.71)
        add(3.14)
        add(1.0)
    }
    .also { println(&quot;Sorting the list&quot;) }
    .sort()</code></pre>
<p>또한 이들은 <code>return 문에</code> 사용될 수 있습니다.</p>
<pre><code class="language-kotlin">fun getRandomInt(): Int {
    return Random.nextInt(100).also {
        writeToLog(&quot;getRandomInt() generated value $it&quot;)
    }
}</code></pre>
<h4 id="lambda-result">Lambda result</h4>
<p><code>let</code>, <code>run</code> 그리고 <code>with</code>는 <code>lambda result</code>를 반환합니다. 따라서 이 두 함수의 결과는 변수에 할당될 수 있습니다. </p>
<pre><code class="language-kotlin">val numbers = mutableListOf(&quot;one&quot;, &quot;two&quot;, &quot;three&quot;)
val countEndsWithE = numbers.run { 
    add(&quot;four&quot;)
    add(&quot;five&quot;)
    count { it.endsWith(&quot;e&quot;) }
}
println(&quot;There are $countEndsWithE elements that end with e.&quot;)</code></pre>
<p>추가적으로 <code>lambda result</code>는 무시될 수 있으며, <code>Scope Function</code>을 변수에 대한 임시 범위를 생성하는데 사용할 수 있습니다.</p>
<pre><code class="language-kotlin">val numbers = mutableListOf(&quot;one&quot;, &quot;two&quot;, &quot;three&quot;)
with(numbers) {
    val firstItem = first()
    val lastItem = last()        
    println(&quot;First item: $firstItem, last item: $lastItem&quot;)
}</code></pre>
<h3 id="functions">Functions</h3>
<h4 id="let"><code>let</code></h4>
<p><code>let</code>은 컨텍스트 객체를 <code>argument</code>(<code>it</code>)처럼 사용할 수 있고 <code>lambda result</code>를 반환합니다. <code>let</code>은 호출 체인의 결과에 대해 하나 이상의 함수를 호출하는데 사용할 수 있습니다.</p>
<pre><code class="language-kotlin">val numbers = mutableListOf(&quot;one&quot;, &quot;two&quot;, &quot;three&quot;, &quot;four&quot;, &quot;five&quot;)
numbers.map { it.length }.filter { it &gt; 3 }.let { 
    println(it)
    // and more function calls if needed
} </code></pre>
<p>만약 코드 블럭이 <code>it</code>을 인자로 가지는 함수 하나만 담고 있다면 <code>lambda expression</code> 대신 <code>::</code>을 사용할 수 있습니다.</p>
<pre><code class="language-kotlin">val numbers = mutableListOf(&quot;one&quot;, &quot;two&quot;, &quot;three&quot;, &quot;four&quot;, &quot;five&quot;)
numbers.map { it.length }.filter { it &gt; 3 }.let(::println)</code></pre>
<p><code>let</code>은 종종 <code>non-null</code> 객체에 코드 블럭을 생성할 때 사용합니다. 또 코드 가독성을 높이기 위해 코드 블럭에 지역 변수를 제공할 때 사용합니다.</p>
<h4 id="with"><code>with</code></h4>
<p><code>with</code>는 컨텍스트 객체를 <code>argument</code>로 전달하지만 내부에선 <code>this</code>를 통해 사용가능합니다. 그리고 <code>lambda result</code>를 반환합니다. <code>with</code>는 코드 블럭 내에서 <code>lambda result</code> 반환없이 함수를 호출할 때 사용할 것을 추천합니다. </p>
<pre><code class="language-kotlin">val numbers = mutableListOf(&quot;one&quot;, &quot;two&quot;, &quot;three&quot;)
// with this object, do the following.
with(numbers) {
    println(&quot;&#39;with&#39; is called with argument $this&quot;)
    println(&quot;It contains $size elements&quot;)
}</code></pre>
<h4 id="run"><code>run</code></h4>
<p><code>run</code>은 컨텍스트 객체를 <code>this</code>를 통해 사용하며, <code>lambda result</code>를 반환합니다. <code>run</code>은 코드 블럭 내에 객체 초기화와 반환 값 계산을 둘다 담고 있는 경우 유용합니다. </p>
<pre><code class="language-kotlin">val service = MultiportService(&quot;https://example.kotlinlang.org&quot;, 80)

val result = service.run {
    port = 8080
    query(prepareRequest() + &quot; to port $port&quot;)
}

// the same code written with let() function:
val letResult = service.let {
    it.port = 8080
    it.query(it.prepareRequest() + &quot; to port ${it.port}&quot;)
}</code></pre>
<h4 id="apply"><code>apply</code></h4>
<p><code>apply</code>는 컨텍스트 객체를 <code>this</code>를 통해 사용하며, 컨텍스트 객체를 반환합니다. <code>apply</code>는 코드 블럭이 값을 반환하지 않고 주로 컨텍스트 객체의 멤버들로 동작하는 경우 사용합니다. 이에 해당하는 예시로는 객체 설정이 있습니다.</p>
<pre><code class="language-kotlin">// apply the following assignments to the object.
val adam = Person(&quot;Adam&quot;).apply {
    age = 32
    city = &quot;London&quot;        
}
println(adam)</code></pre>
<h4 id="also"><code>also</code></h4>
<p><code>also</code>는 컨텍스트 객체를<code>argument</code>(<code>it</code>)처럼 사용할 수 있고, 컨텍스트 객체를 반환합니다. <code>also</code>는 컨텍스트 객체를 인자처럼 사용하는 액션에 유용합니다. 또 객체의 프로퍼티나 함수에 대한 참조가 아니라 객체 자체에 대한 참조가 필요한 액션에 유용합니다. </p>
<pre><code class="language-kotlin">val numbers = mutableListOf(&quot;one&quot;, &quot;two&quot;, &quot;three&quot;)
// and also do the following with the object.
numbers
    .also { println(&quot;The list elements before adding new one: $it&quot;) }
    .add(&quot;four&quot;)</code></pre>
<h3 id="정리">정리</h3>
<table>
<thead>
<tr>
<th>Function</th>
<th>Object reference</th>
<th>Return value</th>
<th>Is extension function</th>
</tr>
</thead>
<tbody><tr>
<td>let</td>
<td>it</td>
<td>Lambda result</td>
<td>Yes</td>
</tr>
<tr>
<td>run</td>
<td>this</td>
<td>Lambda result</td>
<td>Yes</td>
</tr>
<tr>
<td>run</td>
<td>-</td>
<td>Lambda result</td>
<td>No: called without the context object</td>
</tr>
<tr>
<td>with</td>
<td>this</td>
<td>Lambda result</td>
<td>No: takes the context object as an argument.</td>
</tr>
<tr>
<td>apply</td>
<td>this</td>
<td>Context object</td>
<td>Yes</td>
</tr>
<tr>
<td>also</td>
<td>it</td>
<td>Context object</td>
<td>Yes</td>
</tr>
</tbody></table>
<h3 id="takeif-and-takeunless"><code>takeIf</code> and <code>takeUnless</code></h3>
<p>추가적으로 <code>Scope Function</code>에는 <code>takeIf</code>와 <code>takeUnless</code>도 존재합니다. 이 함수들은 <code>call chain</code>에 객체의 상태를 확인하는 작업을 추가하는 함수입니다. <code>takeIf</code>는 조건문을 만족하면 해당 객체를 반환하며, 그렇지 않으면 <code>null</code>을 반환합니다. <code>takeUnless</code>는 <code>takeIf</code>와 반대로 동작합니다. 이 두 함수는 컨텍스트 객체를 <code>argument</code>(<code>it</code>)처럼 사용합니다. </p>
<pre><code class="language-kotlin">val number = Random.nextInt(100)

val evenOrNull = number.takeIf { it % 2 == 0 }
val oddOrNull = number.takeUnless { it % 2 == 0 }
println(&quot;even: $evenOrNull, odd: $oddOrNull&quot;)</code></pre>
<p>두 함수가 <code>null</code>을 반환할 수 있기 때문에 반환된 값을 사용하려는 경우 <code>null check</code>를 해야합니다. 또 두 함수는 다른 <code>Scope Function</code>과 함께 사용할 때 유용합니다.</p>
<pre><code class="language-kotlin">fun displaySubstringPosition(input: String, sub: String) {
    input.indexOf(sub).takeIf { it &gt;= 0 }?.let {
        println(&quot;The substring $sub is found in $input.&quot;)
        println(&quot;Its start position is $it.&quot;)
    }
}</code></pre>
<h3 id="📖-reference">📖 <strong>Reference</strong></h3>
<p><a href="https://kotlinlang.org/docs/reference/scope-functions.html">Kotlin Language - Scope Functions</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[마인드 블루밍] 3. 개발 이슈 - 드래그 및 확대]]></title>
            <link>https://velog.io/@hwi_chance/%EB%A7%88%EC%9D%B8%EB%93%9C-%EB%B8%94%EB%A3%A8%EB%B0%8D-3.-%EA%B0%9C%EB%B0%9C-%EC%9D%B4%EC%8A%88-%EB%93%9C%EB%9E%98%EA%B7%B8-%EB%B0%8F-%ED%99%95%EB%8C%80</link>
            <guid>https://velog.io/@hwi_chance/%EB%A7%88%EC%9D%B8%EB%93%9C-%EB%B8%94%EB%A3%A8%EB%B0%8D-3.-%EA%B0%9C%EB%B0%9C-%EC%9D%B4%EC%8A%88-%EB%93%9C%EB%9E%98%EA%B7%B8-%EB%B0%8F-%ED%99%95%EB%8C%80</guid>
            <pubDate>Tue, 02 Feb 2021 10:59:17 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>❗ 안드로이드 앱 <strong>마인드 블루밍</strong>의 개발 과정을 포스팅한 글입니다.</p>
</blockquote>
<h2 id="🥇-객체-드래그">🥇 객체 드래그</h2>
<p>객체 드래그는 활성 포인터의 최초 위치를 기록📝하고 포인터가 이동한 거리를 계산한 뒤, 개체를 새 위치로 이동시키는 과정을 거쳐 이루어집니다. 단, 이러한 과정 속에서 추가 포인터의 가능성을 올바르게 관리해야 합니다. </p>
<h3 id="❗-추가-포인터-관리">❗ 추가 포인터 관리</h3>
<p>드래그 작업 중이라면 앱은 손가락👆이 추가로 화면에 놓이더라도 기존의 포인터를 계속 추적해야 합니다. 기존 포인터와 후속 포인터의 구별은 <code>ACTION_POINTER_DOWN</code> 이벤트 및 <code>ACTION_POINTER_UP</code> 이벤트를 통해 할 수 있습니다. 두 이벤트는 <code>onTouchEvent()</code> 콜백에 전달됩니다.</p>
<blockquote>
<p><strong>멀티터치 이벤트</strong></p>
</blockquote>
<ul>
<li><code>ACTION_DOWN</code>: 화면을 터치하는 첫 번째 포인터의 이벤트. 이 이벤트가 동작을 시작</li>
<li><code>ACTION_UP</code>: 마지막 포인터가 화면 밖으로 나갈 때 발생하는 이벤트</li>
<li><code>ACTION_MOVE</code>: 누르기 동작 중에 변경사항이 있으면 발생하는 이벤트</li>
<li><code>ACTION_POINTER_DOWN</code>: 추가 포인터 발생 이벤트</li>
<li><code>ACTION_POINTER_UP</code>: 포인터가 두 개 이상인 상황에서 한 포인터가 위로 올라갔을때 발생하는 이벤트</li>
</ul>
<p>아래 코드는 첫 번째 포인터와 추가 포인터를 추적하여 객체 이동 위치를 계산하는 코드입니다. </p>
<pre><code class="language-kotlin">// active pointer: 현재 객체를 이동시키고 있는 포인터
private var mActivePointerId = INVALID_POINTER_ID

override fun onTouchEvent(ev: MotionEvent): Boolean {
    // ScaleGestureDetector가 모든 이벤트를 감시하도록 설정
    mScaleDetector.onTouchEvent(ev)

    // MotionEvent의 작업 가져옴
    val action = MotionEventCompat.getActionMasked(ev)

    when (action) {
        MotionEvent.ACTION_DOWN -&gt; {
            MotionEventCompat.getActionIndex(ev).also { pointerIndex -&gt;
                // 드래그 시작 위치 기록
                mLastTouchX = MotionEventCompat.getX(ev, pointerIndex)
                mLastTouchY = MotionEventCompat.getY(ev, pointerIndex)
            }

            // active pointer 설정
            mActivePointerId = MotionEventCompat.getPointerId(ev, 0)
        }

        MotionEvent.ACTION_MOVE -&gt; {
            // active pointer가 이동한 위치를 기록
            val (x: Float, y: Float) =
                    MotionEventCompat.findPointerIndex(ev, mActivePointerId).let { pointerIndex -&gt;
                        // to: pair를 만드는 키워드
                        MotionEventCompat.getX(ev, pointerIndex) to
                               MotionEventCompat.getY(ev, pointerIndex)
                    }
            // 이동할 위치 계산
            mPosX += x - mLastTouchX
            mPosY += y - mLastTouchY

            // view 무효화. view가 visible 상태이면 곧 `onDraw()`가 자동으로 호출됨
            invalidate()

            // 이동한 위치 기록
            mLastTouchX = x
            mLastTouchY = y
        }

        MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -&gt; {
            // 첫번째 포인터가 사라졌을 때 active pointer가 추가 포인터를 참조하지 않도록 설정
            mActivePointerId = INVALID_POINTER_ID
        }

        MotionEvent.ACTION_POINTER_UP -&gt; {
            MotionEventCompat.getActionIndex(ev).also { pointerIndex -&gt;
                MotionEventCompat.getPointerId(ev, pointerIndex)
                        .takeIf { it == mActivePointerId }
                        ?.run {
                            // active pointer가 위로 올라갔을 때 추가 포인터를 active pointer로 설정
                            val newPointerIndex = if (pointerIndex == 0) 1 else 0
                            mLastTouchX = MotionEventCompat.getX(ev, newPointerIndex)
                            mLastTouchY = MotionEventCompat.getY(ev, newPointerIndex)
                            mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex)
                        }
            }
        }
    }
    return true
}</code></pre>
<h2 id="🥈-드래그하여-화면-이동">🥈 드래그하여 화면 이동</h2>
<p>드래그를 통한 화면 이동은 사용자의 드래그 모션으로 스크롤이 x축과 y축 모두를 따라 이루어지는 것입니다. 이를 위해 <code>GestureDetector.SimpleOnGestureListener</code>에서 <code>onScroll()</code>을 재정의합니다. </p>
<pre><code class="language-kotlin">// viewport: 현재 디스플레이 되고 있는 뷰의 도메인과 범위를 표현하는 사각형
private val mCurrentViewport = RectF(AXIS_X_MIN, AXIS_Y_MIN, AXIS_X_MAX, AXIS_Y_MAX)
// 데이터가 그려질 목표 사각형 (픽셀 좌표계)
private val mContentRect: Rect? = null

private val mGestureListener = object : GestureDetector.SimpleOnGestureListener() {
    // ...
    override fun onScroll(
            e1: MotionEvent,
            e2: MotionEvent,
            distanceX: Float,
            distanceY: Float
    ): Boolean {
        // 스크롤링은 픽셀이 아닌 viewport 기반의 수학을 사용 
        mContentRect?.apply {
            // 픽셀 단위를 viewport 단위로 변경
            val viewportOffsetX = distanceX * mCurrentViewport.width() / width()
            val viewportOffsetY = -distanceY * mCurrentViewport.height() / height()

            // viewport를 변경하고 리프레쉬
            setViewportBottomLeft(
                mCurrentViewport.left + viewportOffsetX,
                mCurrentViewport.bottom + viewportOffsetY
            )
        }
        return true
    } 
}

// 현재 viewport를 주어진 x, y 포지션에 따라 설정
private fun setViewportBottomLeft(x: Float, y: Float) {
    /*
    * Constrains within the scroll range. The scroll range is simply the viewport
    * extremes (AXIS_X_MAX, etc.) minus the viewport size. For example, if the
    * extremes were 0 and 10, and the viewport size was 2, the scroll range would
    * be 0 to 8.
    */
    val curWidth: Float = mCurrentViewport.width()
    val curHeight: Float = mCurrentViewport.height()
    val newX: Float = Math.max(AXIS_X_MIN, Math.min(x, AXIS_X_MAX - curWidth))
    val newY: Float = Math.max(AXIS_Y_MIN + curHeight, Math.min(y, AXIS_Y_MAX))

    mCurrentViewport.set(newX, newY - curHeight, newX + curWidth, newY)
    // 화면을 업데이트하기 위해 뷰를 무효화
    ViewCompat.postInvalidateOnAnimation(this)
}</code></pre>
<h2 id="🥉-터치를-사용하여-확장">🥉 터치를 사용하여 확장</h2>
<p>Android에서는 확장을 위해 <code>ScaleGestureDetector</code>를 제공합니다. </p>
<pre><code class="language-kotlin">private var mScaleFactor = 1f

private val scaleListener = object : ScaleGestureDetector.SimpleOnScaleGestureListener() {

    override fun onScale(detector: ScaleGestureDetector): Boolean {
        mScaleFactor *= detector.scaleFactor

        // 최대 크기와 최소 크기 제한을 둬야함
        mScaleFactor = Math.max(0.1f, Math.min(mScaleFactor, 5.0f))
        invalidate()
        return true
    }
}

private val mScaleDetector = ScaleGestureDetector(context, scaleListener)

override fun onTouchEvent(ev: MotionEvent): Boolean {
    // ScaleGestureDetector 모든 이벤트를 감지하도록 설정
    mScaleDetector.onTouchEvent(ev)
    return true
}

override fun onDraw(canvas: Canvas?) {
    super.onDraw(canvas)

    canvas?.apply {
        save()
        scale(mScaleFactor, mScaleFactor)
        // onDraw() code goes here
        restore()
    }
}    </code></pre>
<h2 id="📖-reference">📖 <strong>Reference</strong></h2>
<ol>
<li><a href="https://developer.android.com/training/gestures/scale">Android Developer - 드래그 및 확대</a></li>
<li><a href="https://developer.android.com/training/gestures/multi">Android Developer - 멀티터치 동작 처리하기</a></li>
<li><a href="https://android.googlesource.com/platform/development/+/master/samples/training/InteractiveChart/src/com/example/android/interactivechart">Example Code</a></li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[마인드 블루밍] 3. 개발 이슈 - 권한 획득]]></title>
            <link>https://velog.io/@hwi_chance/%EB%A7%88%EC%9D%B8%EB%93%9C-%EB%B8%94%EB%A3%A8%EB%B0%8D-3.-%EA%B0%9C%EB%B0%9C-%EA%B6%8C%ED%95%9C-%ED%9A%8D%EB%93%9D</link>
            <guid>https://velog.io/@hwi_chance/%EB%A7%88%EC%9D%B8%EB%93%9C-%EB%B8%94%EB%A3%A8%EB%B0%8D-3.-%EA%B0%9C%EB%B0%9C-%EA%B6%8C%ED%95%9C-%ED%9A%8D%EB%93%9D</guid>
            <pubDate>Sun, 24 Jan 2021 11:27:25 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>❗ 안드로이드 앱 <strong>마인드 블루밍</strong>의 개발 과정을 포스팅한 글입니다.</p>
</blockquote>
<h2 id="권한-요청">권한 요청</h2>
<p><strong>마인드 블루밍</strong>은 사진 업로드 혹은 사진·PDF로 내보내기 등의 이유로 외부 저장소📀에 대한 읽기, 쓰기 권한이 필요합니다.</p>
<p>마인드 블루밍에서는 <code>ActivityResult API</code>를 사용하여 권한 요청 로직을 작성✍했습니다. </p>
<pre><code class="language-kotlin">private val requestMultiplePermissions =
    registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions())
    { permissions -&gt;
        run {
            if (permissions[Manifest.permission.READ_EXTERNAL_STORAGE] == true
                        &amp;&amp; permissions[Manifest.permission.WRITE_EXTERNAL_STORAGE] == true) {
                // permission granted
            } else {
                // permission denied
            }
        }
    }</code></pre>
<p>먼저 <code>ActivityResultLauncher</code> 변수를 생성합니다. <code>ActivityResultLauncher</code>는 <code>registerForActivityResult</code>의 반환값으로, 인자로 주어진 <code>ActivityResultContract</code>를 실행하고 해당 작업이 완료되면 callback📞을 받아 처리하는<code>laucher</code>입니다.</p>
<p>여기서는 <code>RequestMultiplePermissions()</code>가 <code>ActivityResultContract</code>이기 때문에 권한 획득 관련 작업🔨을 수행합니다. </p>
<pre><code class="language-kotlin">private fun setPermissions() {
    if (checkGranted(Manifest.permission.READ_EXTERNAL_STORAGE)
            &amp;&amp; checkGranted(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
        // permission already granted
    } else {
        // permission not granted
        requestMultiplePermissions.launch(
                arrayOf(
                        Manifest.permission.READ_EXTERNAL_STORAGE,
                        Manifest.permission.WRITE_EXTERNAL_STORAGE
                )
        )
    }
}

private fun checkGranted(permission: String): Boolean {
    return ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED
}</code></pre>
<p><code>checkSelfPermission()</code> 메소드를 통해 먼저 권한을 이미 획득했는지 확인🧐합니다. 만약 권한을 획득하지 못했다면 <code>ActivityResultLauncher</code> 변수의 <code>launch()</code> 메소드를 통해 권한 획득 관련 작업을 시작합니다. </p>
<h2 id="can-only-use-lower-16-bits-for-requestcode-error">Can only use lower 16 bits for requestCode Error</h2>
<p><code>ActivityResult API</code>를 사용하여 권한 요청 로직을 개발하던 중 다음과 같은 오류🚫가 발생했습니다. 
<img src="https://images.velog.io/images/hwi_chance/post/9afc8efe-eec9-4fb7-b121-3f0a2cb67672/issue.JPG" alt="error msg"></p>
<p><code>ActivityResult API</code>의 <code>launch()</code> 메소드를 사용하면 <code>FragmentActivity.java</code>의 <code>checkForValidRequestCode()</code> 메소드에서 <code>requestCode</code>의 하위 16비트만 <code>set</code>되어 있는지 확인🧐합니다.
<img src="https://images.velog.io/images/hwi_chance/post/0ed02cc9-b4b8-4429-bac2-887bc7142329/code.JPG" alt="code">
여기서 <code>requestCode</code>는 <code>ActivityResultRegistry.java</code>에서 자동으로 생성되는 유니크한 <code>String key</code>를 통해 만들어집니다. 그렇기 때문에 상위 16비트에 <code>set</code>된 비트가 존재하는 <code>requestCode</code>가 생성될 수 있고, 위와 같은 오류가 발생하는 것입니다. 😥</p>
<p><img src="https://images.velog.io/images/hwi_chance/post/5a0d5169-8772-437a-894e-dc6b4828ac5b/changes.JPG" alt=""></p>
<p>이러한 문제를 해결한 것이 바로 <a href="https://developer.android.com/jetpack/androidx/releases/fragment#1.3.0-alpha05">androidx.fragment 라이브러리 종속성 ver 1.3.0-alpha05</a> 입니다. 👏</p>
<pre><code>// kotlin - build.gradle(app)
implementation &#39;androidx.activity:activity-ktx:1.2.0-rc01&#39;
implementation &#39;androidx.fragment:fragment-ktx:1.3.0-rc01&#39;</code></pre><p>📢 결론적으로 <code>ActivityResult API</code> 사용하면서 <strong>&quot;Can only use lower 16 bits for requestCode&quot;</strong> 오류를 피하기 위해선 위와 같이 <code>1.2.0</code> 버전 이상의 <code>androidx.activity</code> 종속성과 <code>1.3.0-alpha05</code> 버전 이상의 <code>androidx.fragment</code> 종속성 모두 사용해야 합니다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[마인드 블루밍] 2. UI 디자인]]></title>
            <link>https://velog.io/@hwi_chance/%EB%A7%88%EC%9D%B8%EB%93%9C-%EB%B8%94%EB%A3%A8%EB%B0%8D-2.-UI-%EB%94%94%EC%9E%90%EC%9D%B8</link>
            <guid>https://velog.io/@hwi_chance/%EB%A7%88%EC%9D%B8%EB%93%9C-%EB%B8%94%EB%A3%A8%EB%B0%8D-2.-UI-%EB%94%94%EC%9E%90%EC%9D%B8</guid>
            <pubDate>Thu, 21 Jan 2021 09:57:06 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>❗ 안드로이드 앱 <strong>마인드 블루밍</strong>의 개발 과정을 포스팅한 글입니다.</p>
</blockquote>
<h3 id="1-로딩-화면">1. 로딩 화면</h3>
<p><img src="https://images.velog.io/images/hwi_chance/post/6c30c2c4-5311-4e4d-8858-0fdf9c6b61fe/01_Splash%20Screen.png" alt="">
로딩 화면에는 앱 아이콘과 앱 타이틀 로고를 배치합니다.</p>
<h3 id="2-인트로-화면">2. 인트로 화면</h3>
<p><img src="https://images.velog.io/images/hwi_chance/post/28e1af80-6152-4697-8f89-7b3227e9a8f5/02_Intro%20Screen.png" alt="인트로">
인트로 화면은 앱을 소개하는 이미지를 배치하고 우측 하단에 페이지를 표시합니다.</p>
<h3 id="3-아이디어-리스트-화면">3. 아이디어 리스트 화면</h3>
<table>
<thead>
<tr>
<th align="center">기본</th>
<th align="center">선택</th>
</tr>
</thead>
<tbody><tr>
<td align="center"><img src="https://images.velog.io/images/hwi_chance/post/066d2c6a-9584-4c9a-b4f3-c839451d8a29/03_Idea%20List%20Default.png" alt=""></td>
<td align="center"><img src="https://images.velog.io/images/hwi_chance/post/4a914732-9300-4ba8-bd0e-ae2e7eb935ee/04_Idea%20List%20Select.png" alt=""></td>
</tr>
</tbody></table>
<p>앱의 메인 화면이며 탭으로 다이어그램 종류를 구분합니다. 아이디어 리스트 원소를 <code>long touch</code>한 경우 아이디어를 선택하는 로직이 수행됩니다. </p>
<table>
<thead>
<tr>
<th align="center">추가</th>
<th align="center">검색</th>
</tr>
</thead>
<tbody><tr>
<td align="center"><img src="https://images.velog.io/images/hwi_chance/post/c3cc1b7e-5d34-4d65-9d82-3cb1eb9c248f/05_Idea%20List%20Add.png" alt=""></td>
<td align="center"><img src="https://images.velog.io/images/hwi_chance/post/a23ab71f-4bf4-4da8-adf7-ae0fa03d68c2/06_Idea%20List%20Search.png" alt=""></td>
</tr>
</tbody></table>
<p>우측 하단에 <code>floating button</code>을 누르면 <code>bottom sheet</code>가 올라와 생성할 다이어그램을 선택할 수 있게 합니다. 또 우측 상단에 검색 버튼을 누르면 앱 타이틀 부분이 검색할 수 있는 영역으로 바뀌게 됩니다. </p>
<table>
<thead>
<tr>
<th align="center">메뉴</th>
</tr>
</thead>
<tbody><tr>
<td align="center"><img src="https://images.velog.io/images/hwi_chance/post/cd60adc5-2c43-4078-9cd7-62e80eb5d43f/07_Idea%20List%20Menu.png" alt=""></td>
</tr>
</tbody></table>
<p>좌측 상단에 메뉴 버튼을 누르면 앱 아이콘과 로고, 그리고 앱 버전을 확인할 수 있습니다. 또한 내보내기, 즐겨찾기, 설정, 시리즈 보기 등의 네비게이션 버튼이 존재합니다. </p>
<h3 id="4-즐겨찾기--시리즈-보기-화면">4. 즐겨찾기 / 시리즈 보기 화면</h3>
<p><img src="https://images.velog.io/images/hwi_chance/post/3f579c21-6e50-4044-a007-1a53c9854e90/08_Series%20-%20Starred%20Page.png" alt="">
즐겨찾기 혹은 시리즈에 포함된 아이디어 리스트를 볼 수 있는 화면이며 다이어그램 구분이 없습니다. </p>
<h3 id="5-내보내기-화면">5. 내보내기 화면</h3>
<table>
<thead>
<tr>
<th align="center">내보낼 아이디어 선택</th>
<th align="center">내보내기</th>
</tr>
</thead>
<tbody><tr>
<td align="center"><img src="https://images.velog.io/images/hwi_chance/post/a1e1e3dc-b1ce-4747-afb2-9a24c9d722a0/09_Export%20Select.png" alt=""></td>
<td align="center"><img src="https://images.velog.io/images/hwi_chance/post/c20e3dc6-111a-4be5-a4b7-4a503a3b516a/10_Export.png" alt=""></td>
</tr>
<tr>
<td align="center">메뉴에서 내보내기를 선택하면 내보낼 아이디어를 선택하는 화면으로 넘어가며, 사진으로 내보낼지 PDF로 내보낼지를 선택한 후 해당 형식으로 내부 저장소에 저장합니다.</td>
<td align="center"></td>
</tr>
</tbody></table>
<h3 id="6-설정-화면">6. 설정 화면</h3>
<table>
<thead>
<tr>
<th align="center">설정</th>
<th align="center">정렬 설정</th>
<th align="center">테마 설정</th>
</tr>
</thead>
<tbody><tr>
<td align="center"><img src="https://images.velog.io/images/hwi_chance/post/675a2215-56e8-4085-be50-4d104d201d07/11_Setting.png" alt=""></td>
<td align="center"><img src="https://images.velog.io/images/hwi_chance/post/b912c690-ca1b-450a-bd6c-e995a3497c7a/12_List%20Sort%20Setting.png" alt=""></td>
<td align="center"><img src="https://images.velog.io/images/hwi_chance/post/fcea0e18-1b89-44fd-a629-6e2d86af1629/13_Theme%20Setting.png" alt=""></td>
</tr>
</tbody></table>
<h3 id="7-아이디어-보기-화면">7. 아이디어 보기 화면</h3>
<p><img src="https://images.velog.io/images/hwi_chance/post/ae2a535c-ea00-4e35-a999-9f5c6ba7c435/14_Idea%20View%20Page.png" alt=""></p>
<h3 id="8-아이디어-편집-화면">8. 아이디어 편집 화면</h3>
<p><img src="https://images.velog.io/images/hwi_chance/post/44ec8a13-df22-4949-934b-2dd305551c01/15_Idea%20Edit.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Android] 안드로이드 AAC]]></title>
            <link>https://velog.io/@hwi_chance/Android-%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-AAC</link>
            <guid>https://velog.io/@hwi_chance/Android-%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-AAC</guid>
            <pubDate>Tue, 19 Jan 2021 13:00:51 GMT</pubDate>
            <description><![CDATA[<h2 id="앱-아키텍처-원칙">앱 아키텍처 원칙</h2>
<h3 id="관심사-분리-🥇">관심사 분리 🥇</h3>
<p>✍ 코드를 작성할 때 <code>Activity</code> 혹은 <code>Fragment</code>와 같은 UI 기반의 클래스는 📌 <strong>UI 및 OS 상호작용을 처리하는 로직만 포함</strong>해야 합니다. 이는 UI 클래스를 최대한 가볍게 유지하여 Lifecycle 관련 문제를 피하기 위함입니다. </p>
<p>UI 클래스는 무언가를 소유하는 것이 아닌 OS와 앱 사이의 계약을 나타내도록 이어주는 클래스일 뿐이며, 따라서 OS는 메모리 부족과 같은 특정 상황이 발생하면 언제든지 UI 클래스를 제거할 수 있습니다. </p>
<p>요약하자면, UI 클래스로부터 UI, OS 상호작용을 제외한 다른 로직을 분리하여 📌 <strong>UI 클래스에 대한 의존성을 최소화</strong>하는 것이 앱 관리 측면에서 좋습니다. 👍</p>
<h3 id="모델에서-ui-만들기-🥈">모델에서 UI 만들기 🥈</h3>
<p>UI는 Model에서 만들어져야 합니다. Model은 앱의 데이터 처리를 담당하는 컴포넌트로, 📌 <strong>앱의 <code>View</code> 객체 및 앱 컴포넌트와 독립되어 있으므로 앱의 Lifecycle에 영향을 받지 않습니다.</strong> </p>
<p>Model은 가급적 지속적인 Model을 사용하는 것이 👍 좋은데, 이는 지속 Model을 사용하면 OS에서 리소스 확보를 위해 앱을 제거해도 사용자 데이터가 삭제되지 않고 네트워크에 문제가 있어도 앱이 계속 작동📳하게 할 수 있기 때문입니다. </p>
<h2 id="aac란">AAC란?</h2>
<p><strong>AAC</strong>(Android Architecture Components)는 테스트와 유지보수가 쉬운 앱을 디자인할 수 있도록 돕는 라이브러리의 모음입니다. 
<img src="https://images.velog.io/images/hwi_chance/post/f8773023-1064-4259-b0e6-65eced0e2210/final-architecture.png" alt="AAC"></p>
<h3 id="viewmodel-🥇">ViewModel 🥇</h3>
<p>ViewModel은 앱의 Lifecycle을 고려하여 📌 <strong>UI 관련 데이터를 저장하고 관리하는 컴포넌트</strong>입니다.</p>
<p>UI Controller로부터 UI 관련 데이터 저장 및 관리를 분리하여 ViewModel이 담당하도록 하면 다음과 같은 문제를 해결🔧할 수 있습니다.</p>
<ul>
<li><p>안드로이드 프레임워크는 특정 작업이나 완전히 통제할 수 없는 기기 이벤트에 대한 응답으로 UI Controller를 제거하거나 다시 만들 수 있는데, 이런 경우 <strong>UI Controller에 저장된 모든 일시적인 UI 관련 데이터가 삭제</strong>됩니다. 단순한 데이터의 경우 <code>onSaveInstanceState()</code> 메서드를 사용하여 복구할 수 있지만 대용량의 데이터의 경우엔 불가능합니다. </p>
</li>
<li><p>UI Controller에서 데이터를 위한 비동기 호출을 한다면 <strong>메모리 누수 가능성을 방지하기 위한 많은 유지 관리</strong>가 필요하며, 위에서와 같이 데이터를 복귀해야 하는 경우 <strong>비동기 호출을 다시해야 해서 리소스가 낭비</strong>됩니다. </p>
</li>
<li><p>UI Controller에서 DB나 네트워크로부터 데이터를 로드하도록 하면 다른 클래스로 작업이 위임되지 않고 단일 클래스가 혼자서 앱의 모든 작업을 처리하려고 할 수 있습니다. 이 경우 <strong>테스트가 훨씬 더 어려워집니다.</strong> </p>
</li>
</ul>
<p>ViewModel은 <code>ViewModel</code> 클래스를 상속받아 구현합니다.</p>
<pre><code class="language-kotlin">class MyViewModel : ViewModel() {
    /* by lazy: val 데이터의 늦은 초기화 방법 */
    private val users: MutableLiveData&lt;List&lt;User&gt;&gt; by lazy {
        /* .also: scope function */
        MutableLiveDate().also {
            loadUsers()
        }
    }

    fun getUsers(): LiveData&lt;List&lt;User&gt;&gt; {
        return users
    }

    private fun loadUsers() {
        // Do an asynchronous operation to fetch users.
    }
}</code></pre>
<p>UI 클래스에서는 다음과 같은 방법으로 <code>ViewModel</code>에 접근할 수 있습니다.</p>
<pre><code class="language-kotlin">class MyActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        /* by viewModels(): model의 getValue/setValue 동작을
                 viewModels의 getValue/setValue의 동작에 위임  */
        val model: MyViewModel by viewModels()
        model.getUsers().observe(this, Observer&lt;List&lt;User&gt;&gt;{ users -&gt;
            // update UI
        })
    }
}</code></pre>
<p><code>ViewModel</code> 객체는 뷰 또는 <code>LifecycleOwners</code>보다 오래 지속되도록 설계되었으며, <code>ViewModel</code>을 가져올 때 <code>ViewModelProvider</code>에 전달되는 <code>LifecycleOwners</code>에 따라 Lifecycle이 달라집니다. 만약 액티비티가 전달되었다면 액티비티가 종료될 때까지, 프래그먼트가 전달되었다면 프래그먼트가 분리될 때까지 <code>ViewModel</code>은 메모리에 남아 있습니다. 
<img src="https://images.velog.io/images/hwi_chance/post/024e86a5-ba75-4f34-ba4f-266a7a8718f8/viewmodel-lifecycle.png" alt="ViewModel Lifecycle"></p>
<h3 id="livedata-🥈">LiveData 🥈</h3>
<p>LiveData는 📌 <strong>식별 가능한 데이터 홀더 클래스</strong>로 다른 앱 컴포넌트의 Lifecycle을 인식하며, 이를 통해 📌 <strong>활성 상태에 있는 앱 컴포넌트 옵저버에게만 업데이트 정보를 알립니다.</strong> </p>
<p>LiveData를 사용하면 다음과 같은 이점이 있습니다.</p>
<ul>
<li><strong>UI와 데이터 상태의 일치 보장</strong>: LiveData는 <code>Observer Pattern</code>을 따르며 Lifecycle 상태가 변경될 때마다 <code>Observer</code> 객체에 알립니다. 또 앱 데이터의 변경이 발생할 때마다 관찰자에게 알려 UI를 업데이트할 수 있도록 합니다. </li>
<li><strong>메모리 누수 없음</strong>: <code>Observer</code>는 Lifecycle 객체에 결합되어 있으며 연결된 객체의 Lifecycle이 끝나면 자동으로 삭제됩니다. </li>
<li><strong>중지된 활동으로 인한 비정상 종료 없음</strong>: 활동이 백 스택에 있을 때를 비롯하여 <code>Observer</code>가 비활성 상태에 있으면 어떤 LiveData 이벤트도 받지 않습니다.</li>
<li><strong>Lifecycle을 더 이상 수동으로 처리하지 않음</strong>: UI 컴포넌트는 관련 데이터를 관찰하기만 할 뿐 관찰을 중지하거나 다시 시작하지 않으며, LiveData가 이를 자동으로 관리합니다. </li>
<li><strong>최신 데이터 유지</strong>: 컴포넌트가 비활성화되면 다시 활성화될 때 최신 데이터를 수신합니다.</li>
<li><strong>적절한 구성 변경</strong>: 기기 회전과 같은 구성 변경으로 인해 액티비티나 프래그먼트가 다시 생성되면 최신 데이터를 즉시 받게 됩니다. </li>
<li><strong>리소스 공유</strong>: 앱에서 시스템 서비스를 공유할 수 있도록 싱글톤 패턴을 사용하는 LiveData 객체를 확장하여 시스템 서비스를 래핑할 수 있습니다.</li>
</ul>
<p>LiveData 객체는 다음과 같은 순서로 사용됩니다.</p>
<ol>
<li><code>ViewModel</code> 클래스 내에서 특정 유형의 데이터를 보유할 <code>LiveData</code>의 인스턴스를 만듭니다.</li>
<li><code>onChanged()</code> 메서드를 정의하는 <code>Observer</code> 객체를 UI Controller에 만듭니다. <code>onChanged()</code> 메서드는 <code>LiveData</code> 객체가 보유한 데이터가 변경될 경우 발생하는 작업을 제어합니다.</li>
<li><code>observe()</code> 메서드를 사용하여 <code>LiveData</code> 객체에 <code>Observer</code> 객체를 연결합니다. </li>
<li><code>LiveData</code> 객체를 업데이트하는 경우 <code>MutableLiveData</code> 클래스는 <code>setValue(T)</code> 또는 <code>postValue(T)</code> 메서드로 <code>LiveData</code> 객체에 저장된 값을 수정합니다.</li>
</ol>
<h3 id="room-🥉">Room 🥉</h3>
<p>Room 라이브러리는 SQLite에 추상화 레이어를 제공하여 원활한 DB 액세스를 지원하고 SQLite를 완벽히 활용할 수 있게 하는 라이브러리입니다.
<img src="https://images.velog.io/images/hwi_chance/post/272f5235-dff1-470b-aefd-5ac3c9c5d1cc/room_architecture.png" alt="Room Architecture"></p>
<blockquote>
<p><a href="https://velog.io/@hwi_chance/Kotlin-7%EC%9E%A5.-%EC%95%B1-%EA%B0%9C%EB%B0%9C-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4#room-orm-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC">Room 라이브러리 사용법</a></p>
</blockquote>
<p>Room 라이브러리를 사용하면 앱을 실행하는 기기에서 앱 데이터의 캐시를 만들 수 있으며, 이 캐시를 통해 사용자는 인터넷 연결 여부와 관계없이 앱에 있는 주요 정보를 일관된 형태로 볼 수 있습니다.</p>
<h3 id="📖-reference">📖 <strong>Reference</strong></h3>
<ol>
<li><a href="https://developer.android.com/topic/libraries/architecture">Android 아키텍처 구성요소</a></li>
<li><a href="https://developer.android.com/jetpack/guide">앱 아키텍처 가이드</a></li>
<li><a href="https://developer.android.com/topic/libraries/architecture/viewmodel">ViewModel 개요</a></li>
<li><a href="https://developer.android.com/topic/libraries/architecture/livedata">LiveData 개요</a></li>
<li><a href="https://developer.android.com/topic/libraries/architecture/room">Room 지속성 라이브러리</a></li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[마인드 블루밍] 1. 서비스 기획]]></title>
            <link>https://velog.io/@hwi_chance/%EB%A7%88%EC%9D%B8%EB%93%9C-%EB%B8%94%EB%A3%A8%EB%B0%8D-1.-%EC%84%9C%EB%B9%84%EC%8A%A4-%EA%B8%B0%ED%9A%8D</link>
            <guid>https://velog.io/@hwi_chance/%EB%A7%88%EC%9D%B8%EB%93%9C-%EB%B8%94%EB%A3%A8%EB%B0%8D-1.-%EC%84%9C%EB%B9%84%EC%8A%A4-%EA%B8%B0%ED%9A%8D</guid>
            <pubDate>Mon, 18 Jan 2021 11:10:51 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>❗ 안드로이드 앱 <strong>마인드 블루밍</strong>의 개발 과정을 포스팅한 글입니다. </p>
</blockquote>
<h2 id="1-무엇을-하는-서비스인가">1. 무엇을 하는 서비스인가?</h2>
<p><strong>마인드 블루밍</strong>은 갑자기 떠오른 아이디어💡를 <strong>마인드맵</strong> 혹은 <strong>플로우 차트</strong>로 도식화할 수 있는 안드로이드 앱📱입니다. </p>
<h2 id="2-기본적인-서비스-구조">2. 기본적인 서비스 구조</h2>
<p><img src="https://images.velog.io/images/hwi_chance/post/38711ffc-b50b-412f-890e-96ba9774b22e/baseStructure.jpg" alt="기본 구조">
<strong>마인드 블루밍</strong>의 가장 기본적인 서비스 구조는 <strong>📌&quot;아이디어를 도식화하여 저장하고 저장된 아이디어 보는 것&quot;</strong>입니다.</p>
<h2 id="3-사용자의-예상-사용-방식">3. 사용자의 예상 사용 방식</h2>
<p><img src="https://images.velog.io/images/hwi_chance/post/f982cba2-7545-4013-a14d-16527d082987/usageFlow.jpg" alt="사용 흐름"></p>
<h2 id="4-마인드맵">4. 마인드맵</h2>
<p><img src="https://images.velog.io/images/hwi_chance/post/8d8f72b7-386d-4401-8ac2-66013eb81021/KakaoTalk_20210118_230023956.png" alt="마인드맵"></p>
<blockquote>
<p>🌐 <a href="https://www.mindmeister.com/1751527526">https://www.mindmeister.com/1751527526</a></p>
</blockquote>
<h2 id="5-어플리케이션-맵">5. 어플리케이션 맵</h2>
<p><img src="https://images.velog.io/images/hwi_chance/post/e6e3d9c7-4541-4712-8fe8-81dde8a8334b/Application%20Map.jpg" alt="어플리케이션 맵"></p>
<h2 id="6-서비스-플로우-차트">6. 서비스 플로우 차트</h2>
<p><img src="https://images.velog.io/images/hwi_chance/post/4ee9a7b5-5330-4b16-8ac7-d9b9afaef2c9/flowchart.jpg" alt="플로우차트"></p>
<h2 id="7-화면-흐름도">7. 화면 흐름도</h2>
<p><img src="https://images.velog.io/images/hwi_chance/post/0c054dd0-28d2-4e54-ab23-3694816ac0e9/screen%20flow.jpg" alt="화면 흐름도"></p>
]]></description>
        </item>
    </channel>
</rss>