<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>hhi-5258.log</title>
        <link>https://velog.io/</link>
        <description>안드로이드, flutter 개발자</description>
        <lastBuildDate>Wed, 31 May 2023 05:53:22 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>hhi-5258.log</title>
            <url>https://images.velog.io/images/hhi-5258/profile/03bc7962-3bf4-442b-a201-2d24c591daa3/social.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. hhi-5258.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/hhi-5258" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[flutter web 배포]]></title>
            <link>https://velog.io/@hhi-5258/flutter-web-%EB%B0%B0%ED%8F%AC</link>
            <guid>https://velog.io/@hhi-5258/flutter-web-%EB%B0%B0%ED%8F%AC</guid>
            <pubDate>Wed, 31 May 2023 05:53:22 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>아래의 과정으로 배포를 진행하겠다.</p>
</blockquote>
<ol>
<li>Github actions 로 CI/CD 적용</li>
<li>aws s3 에 호스팅 </li>
<li>cloudfront 로 도메인 주소 적용</li>
</ol>
<h3 id="github-action">github action</h3>
<p>Github Repository 생성 후 .github/workflows 폴더 내에 yaml 파일을 생성한다. yaml 파일을 통해 Github action 의 workflow 가 생성된다.
workflow 파일을 따라 CI/CD 작업이 진행되는데 workflow 파일을 살펴보겠다.</p>
<pre><code class="language-yml">name: Deploy to production /// action 의 이름 

on: /// action 이 trigger 되는 방식 git 의 main branch 
  push:
    branches:
      - main
    tags:
      - &quot;v*.*.*&quot;

  workflow_dispatch:

jobs:
  build-web:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Setup Flutter SDK
        uses: subosito/flutter-action@v2
        with:
          flutter-version: &quot;3.7.1&quot;
          channel: &quot;stable&quot;

      - name: Enable desktop
        run: flutter config --enable-web

      - name: Flutter get packages
        run: flutter pub get

      - name: Build Runner &amp; version
        run: flutter packages pub run build_runner build --delete-conflicting-outputs

      - name: Flutter build app
        run: flutter build web --release --base-href / --dart-define=&quot;BASE_URL=https://reward-api.danbicorp.com/api&quot;

      - name: Use random for fix cache
        run: |
          ran=$RANDOM
          echo $ran
          sed -i &quot;s|flutter\.js|flutter.js?v=$ran|&quot; ./build/web/index.html
          sed -i &quot;s|main\.dart\.js|main.dart.js?v=$ran|&quot; ./build/web/flutter.js

      - name: Archive Production Artifact
        uses: actions/upload-artifact@master
        with:
          name: web-build
          path: build/web

  deploy-web:
    name: Deploy Website to AWS Hosting
    needs: build-web
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Repo
        uses: actions/checkout@v3

      - name: Download Artifact
        uses: actions/download-artifact@master
        with:
          name: web-build
          path: build/web 

      - name: Configure AWS Credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ${{ secrets.AWS_REGION }}

      - name: Upload to S3 🚀
        uses: jakejarvis/s3-sync-action@master
        with:
          args: --follow-symlinks --delete
        env:
          AWS_S3_BUCKET: ${{ secrets.AWS_S3_BUCKET_PROD }}
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          AWS_REGION: ${{ secrets.AWS_REGION }}
          SOURCE_DIR: &#39;build/web&#39;

      - name: Create CloudFront Cache Invalidation ☁️
        uses: chetan/invalidate-cloudfront-action@master
        env:
          DISTRIBUTION: ${{ secrets.AWS_CLOUDFRONT_DISTRIBUTION_ID_PROD }}
          PATHS: &#39;/*&#39;
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          AWS_REGION: ${{ secrets.AWS_REGION }}</code></pre>
<h3 id="s3-호스팅">s3 호스팅</h3>
<ol>
<li>AWS S3에 접근하기 위한 IAM 계정 만들기</li>
<li>S3 bucket 생성</li>
<li>S3 bucket 정적 웹 사이트 호스팅 활성화</li>
<li>버킷 웹 사이트 엔드포인트 복사</li>
</ol>
<h3 id="cloudfront-로-도메인-주소-적용">cloudfront 로 도메인 주소 적용</h3>
<p>cloudFront 를 이용하여 s3 에서 생성한 버킷의 엔드포인트를 생성한 도메인 주소로 연결한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Parent-Child Widget method 호출 - 2]]></title>
            <link>https://velog.io/@hhi-5258/Parent-Child-Widget-method-%ED%98%B8%EC%B6%9C-2</link>
            <guid>https://velog.io/@hhi-5258/Parent-Child-Widget-method-%ED%98%B8%EC%B6%9C-2</guid>
            <pubDate>Mon, 16 Jan 2023 07:53:27 GMT</pubDate>
            <description><![CDATA[<h2 id="parent---child-메소드-호출">Parent -&gt; Child 메소드 호출</h2>
<blockquote>
<p>GlobalKey사용
child Widget 의 State를 GlobalKey 로 전달</p>
</blockquote>
<h3 id="global-key-란">Global Key 란?</h3>
<p><img src="https://velog.velcdn.com/images/hhi-5258/post/8269380e-2e66-4409-8bc8-3eebb0b5cfc3/image.png" alt="">
key는 무엇인가?</p>
<ol>
<li>위젯의 state를 보존 ( 예- 체크박스의 상태, 텍스트 필드의 문자들이 입력되고 있는지 등)</li>
<li>위젯을 식별
Global Key : 위젯의 state를 외부 위젯에서 접근</li>
</ol>
<h3 id="적용-방법">적용 방법</h3>
<ol>
<li>child Widget의 State를 사용하기 위해 _삭제</li>
<li>Parent Widget 에서 global key 생성</li>
<li>child Widget 생성 시 global Key 선언</li>
<li>global key로 child 의 state 내부 method 호출 </li>
</ol>
<ul>
<li><p>child Widget Code</p>
<pre><code>class ChildWidget extends StatefulWidget {
const ChildWidget({Key? key,}) : super(key: key);

@override
State&lt;ChildWidget&gt; createState() =&gt; ChildWidgetState();
}
</code></pre></li>
</ul>
<p>class ChildWidgetState extends State<ChildWidget> {
  Color _color = Colors.red;</p>
<p>  void changeColor(Color color) {
    setState(() {
      _color = color;
    });
  }</p>
<p>  @override
  Widget build(BuildContext context) {
    return InkWell(
      child: Container(
        width: 200,
        height: 200,
        color: _color,
        child: const Text(&#39;Child Widget&#39;),
      ),
    );
  }
}</p>
<pre><code>
- parent Widget Code</code></pre><p>class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});</p>
<p>  final String title;</p>
<p>  @override
  State<MyHomePage> createState() =&gt; _MyHomePageState();
}</p>
<p>class _MyHomePageState extends State<MyHomePage> {
  final GlobalKey<ChildWidgetState> _globalKey = GlobalKey();</p>
<p>  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            ChildWidget(
              key: _globalKey,
            ),
            const SizedBox(height: 10,),
            InkWell(
              onTap: () {
                _globalKey.currentState?.changeColor(Colors.red);
              },
              child: Container(
                height: 50,
                color: Colors.yellow,
                child: const Text(&#39;Parent Widget Button&#39;)
              ),
            )
          ],
        ),
      ),
    );
  }
}
```</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Parent-Child Widget  method 호출 - 1]]></title>
            <link>https://velog.io/@hhi-5258/Parent-Child-Widget-method-%ED%98%B8%EC%B6%9C-1</link>
            <guid>https://velog.io/@hhi-5258/Parent-Child-Widget-method-%ED%98%B8%EC%B6%9C-1</guid>
            <pubDate>Mon, 16 Jan 2023 02:31:04 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>flutter 는 widget들의 조합이다. 화면이 복잡해져 위젯이 많아 질수록 코드는 거대 해진다. 가독성을 위해 위젯을 분할하는 것이 중요하다. 위젯을 별도의 class 로 분리했을 때 child-Parent widget 사이의 method를 호출하는 법에 대해 알아보았다.</p>
</blockquote>
<h2 id="child---parent-함수-호출">Child -&gt; Parent 함수 호출</h2>
<blockquote>
<p>Child instance 생성 시 parameter로 Parent Widget의 callBack 함수 전달, child 에서  call</p>
</blockquote>
<ul>
<li><p>child widget</p>
<pre><code>class ChildWidget extends StatefulWidget {
final VoidCallback onTapped;
const ChildWidget({Key? key, required this.onTapped}) : super(key: key);

@override
State&lt;ChildWidget&gt; createState() =&gt; _ChildWidgetState();
}
</code></pre></li>
</ul>
<p>class _ChildWidgetState extends State<ChildWidget> {
  @override
  Widget build(BuildContext context) {
    return InkWell(
      onTap: widget.onTapped.call,
      child: Container(
        width: 200,
        height: 200,
        color: Colors.red,
      ),
    );
  }
}</p>
<pre><code>- parnet widget</code></pre><pre><code>        ChildWidget(
          onTap: () {
            Get.snackbar(&#39;알림&#39;, &#39;자식 위젯 onTapped&#39;);
          },
        )</code></pre><pre><code>
&gt; ### CallBack 함수의 parameter 로 data 를 전달하고 싶은 경우?
callback 함수의 정의는 다음과 같다.</code></pre><p>typedef void VoidCallback();</p>
<pre><code>데이터를 담고 싶다면 typedef 로 함수를 정의하여 child 로 전달할 수 있다.

- child Widget Code</code></pre><p>typedef Int2VoidFunc = void Function(int arg);</p>
<p>class ChildWidget extends StatefulWidget {
  final Int2VoidFunc onTapped;
  const ChildWidget({Key? key, required this.onTapped}) : super(key: key);</p>
<p>  @override
  State<ChildWidget> createState() =&gt; _ChildWidgetState();
}</p>
<p>class _ChildWidgetState extends State<ChildWidget> {
  @override
  Widget build(BuildContext context) {
    return InkWell(
      onTap: () {
        widget.onTapped(5);
      },
      child: Container(
        width: 200,
        height: 200,
        color: Colors.red,
      ),
    );
  }
}</p>
<pre><code>
- Parent Widget Code</code></pre><pre><code>        ChildWidget(
          onTapped: (arg) {
            Get.snackbar(&#39;알림&#39;, &#39;자식 위젯 onTapped, $arg&#39;);
          },
        )</code></pre><pre><code>
</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[RegisterForActivityResult]]></title>
            <link>https://velog.io/@hhi-5258/RegisterForActivityResult</link>
            <guid>https://velog.io/@hhi-5258/RegisterForActivityResult</guid>
            <pubDate>Tue, 15 Jun 2021 15:40:58 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p> startActivityForResult()와 onActivityResult() 가 deprecated 되었습니다. 이를 대신하여 AndroidX Activity 1.2.0-alpha02 와 Fragment 1.3.0-alpha02에 도입된 Activity Result API를 사용합니다.</p>
</blockquote>
<h2 id="registerforactivityresult-란">registerForActivityResult() 란..?</h2>
<p>결과를 얻기 위해 활동을 시작할 때, 메모리 부족으로 인해 프로세스와 활동이 소멸될 수 있습니다(특히, 카메라 사용과 같이 메모리를 많이 사용하는 작업의 경우 소멸 확률이 매우 높음).</p>
<p>따라서, Activity Result API는 다른 활동을 실행하는 코드 위치에서 결과 콜백을 분리합니다. 결과 콜백은 프로세스와 활동을 다시 생성할 때 사용할 수 있어야 하므로 다른 활동을 실행하는 로직이 사용자 입력 또는 기타 비즈니스 로직을 기반으로만 발생하더라도 활동이 생성될 때마다 콜백을 무조건 등록해야 합니다.</p>
<p>ComponentActivity 또는 Fragment에 있을 때, Activity Result API에서 제공하는 registerForActivityResult() API를 통해 결과 콜백을 등록할 수 있습니다. registerForActivityResult()는 ActivityResultContract 및 ActivityResultCallback을 가져와서 다른 활동을 실행하는 데 사용할 ActivityResultLauncher를 반환합니다.</p>
<h2 id="적용-예제">적용 예제</h2>
<h3 id="mainactivity">MainActivity</h3>
<pre><code class="language-kotlin">class MainActivity : AppCompatActivity() {

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

        startSecondActivity()
    }

    private fun startSecondActivity() {
        val intent = Intent(this@MainActivity, SecondActivity::class.java)
        activityResultLauncher.launch(intent)
    }

    private val activityResultLauncher =
        registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
            if (it.resultCode == RESULT_OK) {
                it.data?.let { intent -&gt;
                    if (intent.hasExtra(SecondActivity.RESULT_DATA)) {
                        Toast.makeText(
                            this@MainActivity,
                            intent.getStringExtra(SecondActivity.RESULT_DATA),
                            Toast.LENGTH_SHORT
                        ).show()
                    }
                }
            }
        }
}</code></pre>
<h3 id="secondactivity">SecondActivity</h3>
<pre><code class="language-kotlin">class SecondActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_second)

        val button = findViewById&lt;Button&gt; (R.id.button_close)
        button.setOnClickListener {
            val intent = Intent().putExtra(RESULT_DATA, &quot;hello&quot;)
            setResult(RESULT_OK, intent)
            finish()
        }
    }

    companion object {
        const val RESULT_DATA = &quot;result_data&quot;
    }
}</code></pre>
<hr>
<h2 id="reference">Reference</h2>
<p><a href="https://developer.android.com/training/basics/intents/result?hl=ko">[android developers] 활동에서 결과 가져오기</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Ch2. 안드로이드와 모델-뷰-컨트롤러]]></title>
            <link>https://velog.io/@hhi-5258/Ch2.-%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C%EC%99%80-%EB%AA%A8%EB%8D%B8-%EB%B7%B0-%EC%BB%A8%ED%8A%B8%EB%A1%A4%EB%9F%AC</link>
            <guid>https://velog.io/@hhi-5258/Ch2.-%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C%EC%99%80-%EB%AA%A8%EB%8D%B8-%EB%B7%B0-%EC%BB%A8%ED%8A%B8%EB%A1%A4%EB%9F%AC</guid>
            <pubDate>Sat, 29 May 2021 11:39:44 GMT</pubDate>
            <description><![CDATA[<h3 id="data-class">data class</h3>
<p>비즈니스 로직을 처리하는 함수보다는 데이터를 저장하는 속성을 갖는 클래스. 데이터를 처리하는데 필요한 아래의 함수들을 코틀린 컴파일러가 자동으로 생성해줍니다.</p>
<ul>
<li>equals() : 클래스 인스턴스끼리 각 속성의 값을 비교</li>
<li>hashCode() : 인스턴스를 컬렉션(ex, HashMap) 에 저장할 때 사용할 키 값들을 생성</li>
<li>toString() : 속성값을 문자열로 출력</li>
</ul>
<h3 id="mvc-아키텍처">MVC 아키텍처</h3>
<ul>
<li>Model : 데이터를 보존, 관리</li>
<li>View : 화면에서 볼 수 있는 것</li>
<li>Controller : 뷰 객체에 의해 발생하는 다양한 이벤트에 응답, 모델과 뷰의 중간에서 주고받는 데이터를 관리.
MVC 를 사용하면 각 계층이 분리되어 앱을 설계하고 이해하는데 도움이 됩니다. 또한, 클래스를 재사용하기 쉽습니다. 크고 복잡한 앱에서는 컨트롤러가 커지거나 복잡해질 수 있습니다. 그럴 땐 MVVM 아키텍처가 대안이 될 수 있습니다.</li>
</ul>
<h3 id="화면-픽셀-밀도">화면 픽셀 밀도</h3>
<ul>
<li>px : pixel, 픽셀은 장치의 화면밀도에 적합하게 조정되지 않음.</li>
<li>dp : density-independent pixel(밀도 독립적 픽셀), 1dp는 항상 장치 화면의 1/160 인치, 화면 밀도와 무관하게 일정한 크기를 갖는다. </li>
<li>sp : scale-independent pixel(크기 독립적 픽셀), 사용자의 폰트 크기 설정을 고려, 텍스트 크기 설정을 위해 사용</li>
</ul>
<hr>
<h2 id="reference">Reference</h2>
<p>[실무에 바로 적용하는 안드로이드 프로그래밍], 크리스틴 마시캐노, 브라이언 가드너, 빌 필립스, 크리스 스튜어트</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Room]]></title>
            <link>https://velog.io/@hhi-5258/Room</link>
            <guid>https://velog.io/@hhi-5258/Room</guid>
            <pubDate>Sat, 15 May 2021 07:20:11 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>안드로이드의 내부 DB 로 자주 사용되는 Room, Realm, SQLite 를 비교하고 Room 에 대해 좀 더 자세히 알아보겠습니다.</p>
</blockquote>
<h2 id="room-vs-sqlite">Room VS SQLite</h2>
<p>Room은 SQLite 에 대한 추상화 레이어를 제공하여 원활한 데이터베이스 액세스를 지원하며 ORM 방식입니다. SQLite 와 비교하여 Room 의 장점들은 다음과 같습니다.</p>
<ul>
<li>컴파일 타임에서 에러 처리</li>
<li>Boiler plate code( 예) getter, setter ) 필요 없음</li>
<li>schema 변경 시 자동 업데이트</li>
<li>Rxjava 를 위한 Observable 제공</li>
</ul>
<h2 id="room-vs-realm">Room VS Realm</h2>
<p>그렇다면 현재 많이 사용되는 Room 과 Realm 을 비교해 보겠습니다. Realm 은 안정성이 좋고, 처리 속도가 빨라 대용량 데이터 처리에 좋습니다. 그러나, 내부 라이브러리가 커 용량을 많이 차지 합니다. 또한, SQL 쿼리를 사용했다면 러닝 커브가 있습니다.
큰 용량의 데이터 처리가 아닌 경우라면 Room 또한 좋습니다.</p>
<h2 id="room-이란">Room 이란..?</h2>
<p>Room은 SQLite에 대한 추상화 레이어를 제공하여 원활한 데이터베이스 액세스를 지원하는 동시에 SQLite를 완벽히 활용합니다. 앱은 Database 를 사용하여 Dao 를 가져오고, 가져온 Dao 로 Entity 들을 Database 에 저장하고 변경합니다. </p>
<h3 id="room-의-구성">Room 의 구성</h3>
<ul>
<li>Database : Dao 를 포함한 액세스 포인트 역할</li>
<li>Entity : 테이블 구조 정의 예) 칼럼 명, 칼럼 타입</li>
<li>Dao(Database Access Object) : db 접근 메소드들(예) insert, delete) 을 정의  </li>
</ul>
<h2 id="room-구현">Room 구현</h2>
<p>build.gradle 파일에 다음의 dependency 를 추가합니다.</p>
<pre><code class="language-kotlin">  def room_version = &quot;2.3.0&quot;
  implementation &quot;androidx.room:room-runtime:$room_version&quot;
  kapt &quot;androidx.room:room-compiler:$room_version&quot;</code></pre>
<h3 id="entity">Entity</h3>
<pre><code class="language-kotlin">@Entity
data class User (
    @PrimaryKey val uId : Int,
    @ColumnInfo(name = &quot;first_name&quot;) val firstName: String?,
    @ColumnInfo(name = &quot;last_name&quot;) val lastName: String?,
    @Ignore val isChecked: Boolean
)</code></pre>
<p>@Entity(tableName = &quot;테이블 이름&quot;)
테이블 이름을 지정할 수 있습니다. 기본값은 클래스명 입니다.
@PrimaryKey 
PrimaryKey 를 지정합니다.
@ColumnInfo
칼럼 명을 지정합니다. 기본 값은 변수명입니다.
@Ignore
DB 에 저장하긴 싫지만 이 클래스 내에 있어야 하는 변수에 사용합니다.</p>
<h3 id="dao">Dao</h3>
<pre><code class="language-kotlin">@Dao
interface UserDao {
    @Query(&quot;SELECT * FROM user&quot;)
    fun getAll() : List&lt;User&gt;

    @Query(&quot;SELECT * FROM user WHERE uId IN (:userIds)&quot;)
    fun getAllByIds(userIds: IntArray) : List&lt;User&gt;

    @Insert
    fun insertAll(vararg users: User)

    @Delete
    fun delete(user: User)
}</code></pre>
<p>@Query
Query 문을 작성합니다. 쿼리에 매개변수를 전달하려면 <code>:변수명</code> 방식으로 사용합니다.</p>
<h3 id="database">Database</h3>
<pre><code class="language-kotlin">@Database(entities = [User::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
    abstract fun userDao(): UserDao

    companion object {
        private var INSTANCE: AppDatabase? = null
        fun getInstance(context: Context): AppDatabase? {
            if (INSTANCE == null) {
                synchronized(AppDatabase::class) {
                    INSTANCE = Room.databaseBuilder(
                        context.applicationContext,
                        AppDatabase::class.java,
                        &quot;user.db&quot;
                    ).build()
                }
            }

            return INSTANCE
        }
    }
}</code></pre>
<p>Database 는 RoomDatabase() 를 상속받는 abstract 클래스여야 합니다. <code>@Database</code> 어노테이션에는 해당 database 와 관련된 Entity 의 목록을 나열해야 합니다. 또한, 클래스 내에는 arguments 가 0개인 @Dao 를 반환하는 abstract 메소드를 포함해야 합니다. 
런타임 시 <code>Room.databaseBuilder()</code> 또는 <code>Room.inMemoryDatabaseBuilder()</code> 를 호출하여 Database 인스턴스를 가져올 수 있습니다. Database 객체를 Singleton 으로 생성합니다.</p>
<h3 id="typeconverter">TypeConverter</h3>
<p>Room 에 Primitive type 이 아닌 값을 저장하기 위해서는 TypeConverter 를 사용하여 Room 에게 특정 타입을 데이터베이스에 저장하고, 가져오는 방법을 알려주어야 합니다. 
TypeConverter 클래스를 생성하고, 생성한 클래스를 Database 클래스에 추가하여 사용합니다.</p>
<h4 id="typeconverter-class">TypeConverter class</h4>
<pre><code class="language-kotlin">class UserTypeConverters {

    @TypeConverter
    fun fromDate(date: Date?): Long? {
        return date?.time
    }

    @TypeConverter
    fun toDate(millisSinceEpoch: Long?): Date? {
        return millisSinceEpoch?.let {
            Date(it)
        }
    }

    @TypeConverter
    fun toUUID(uuid: String?): UUID? {
        return UUID.fromString(uuid)
    }

    @TypeConverter
    fun fromUUID(uuid: UUID?): String? {
        return uuid?.toString()
    }
}</code></pre>
<h4 id="database-class">DataBase Class</h4>
<pre><code class="language-kotlin">@Database(entities = [Crime::class], version = 1)
@TypeConverters(UserTypeConverters::class)
abstract class AppDatabase : RoomDatabase() {
}</code></pre>
<h3 id="room-비동기-처리">Room 비동기 처리</h3>
<p>DB 에 데이터를 처리하는 일은 시간이 많이 걸리 수 있기 때문에 메인스레드에서 처리할 수 없습니다. 그래서 백그라운드 스레드에서 처리를 해주어야 합니다. 비동기 처리의 방법에는 AsyncTask, Rxjava, coroutin 등 다양한 방법이 있습니다. 이번에는 코루틴을 사용해 보겠습니다.</p>
<pre><code class="language-kotlin">    val db = AppDatabase.getInstance(applicationContext)

        lifecycleScope.launch(Dispatchers.IO) {
            db!!.userDao().insertAll(User(0, &quot;han&quot;, &quot;hi&quot;, true))
        }</code></pre>
<hr>
<h2 id="reference">Reference</h2>
<p><a href="https://medium.com/%EC%8A%AC%EA%B8%B0%EB%A1%9C%EC%9A%B4-%EA%B0%9C%EB%B0%9C%EC%83%9D%ED%99%9C/%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-room-%EC%82%AC%EC%9A%A9%EB%B2%95-1b7bd07b4cee">슬기로운 개발생활[안드로이드 Room 사용법]</a>
<a href="https://developer.android.com/training/data-storage/room?hl=ko">안드로이드 developers[Room을 사용하여 로컬 데이터베이스에 데이터 저장]</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Ch.4 SIS vs AAC ViewModel]]></title>
            <link>https://velog.io/@hhi-5258/SIS-vs-AAC-ViewModel-vs-LiveData</link>
            <guid>https://velog.io/@hhi-5258/SIS-vs-AAC-ViewModel-vs-LiveData</guid>
            <pubDate>Tue, 11 May 2021 15:25:24 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>액티비티를 회전하여 액티비티가 소멸되고 재생성되거나, 프로세스가 종료되어 액티비티가 유실될 때도 있습니다.
이런 경우들의 경우 기존 액티비티의 데이터를 어떻게 유지하는지 다양한 방법들을 살펴 보겠습니다.</p>
</blockquote>
<h2 id="aac-viewmodel">AAC ViewModel</h2>
<h3 id="aac-viewmodel-이란">AAC ViewModel 이란...?</h3>
<p>AAC의 ViewModel은 수명 주기를 고려해 관련 데이터를 저장하고 관리하도록 설계된 클래스이며 안드로이드 jetpack 의 lifecylce-extensions 라이브러리에 포함되어 제공됩니다. ViewModel 인스턴스는 액티비티의 생명주기와 연동됩니다. ViewModel 인스턴스는 액티비티 상태 변화와 무관하게 액티비티가 종료될 때까지 (예, 사용자가 백 버튼을누를 때) 메모리에 남아 있다가 액티비티가 종료되면 소멸됩니다.</p>
<h3 id="사용-예제">사용 예제</h3>
<p>app 수준의 gradle 에 <code>lifecycle-extensions</code> dependency를 추가합니다.</p>
<pre><code class="language-kotlin">dependencies {
    ...
    implementation &#39;androidx.lifecycle:lifecycle-extensions:2.2.0&#39;
}</code></pre>
<p>ViewModel 은 특정 액티비티 화면과 연동되며, 모델 데이터를 화면에 보여주는 기능을 수행합니다. ViewModel 을 상속하는 ViewModel 을 생성해 줍니다.</p>
<pre><code class="language-kotlin">class QuizViewModel : ViewModel() {
    // ViewModel 인스턴스가 소멸되기 전에 호출되므로 클린업할 것이 있으면 추가
   override fun onCleared() {
        super.onCleared()
        Log.d(TAG, &quot;ViewModel instance about to be destroyed&quot;)
    }
}</code></pre>
<p>연동되는 액티비티에 ViewModel 인스턴스를 연결합니다. AAC ViewModel 클래스는 개발자가 직접 생성자 메서드로 인스턴스 생성이 불가능합니다. ViewModelProvider를 사용하여 인스턴스를 생성해야 합니다. ViewModelProvider 의 생성자로 Lifecycle 을 동일시 할 액티비티를 제공합니다.</p>
<pre><code class="language-kotlin">class MainActivity : AppCompatActivity() {
    ...
    override fun onCreate(savedInstanceState: Bundle?) {
        val provider: ViewModelProvider = ViewModelProVider(this)
        val quizViewModel = provider.get(QuizViewModel::class.java)
    }
}</code></pre>
<h3 id="viewmodel-과-activity-의-관계">ViewModel 과 Activity 의 관계</h3>
<p>액티비티는 ViewModel 을 참조하지만, ViewModel 은 액티비티를 참조하지 않습니다. 
화면 회전 시에 액티비티 인스턴스는 소멸되지만, ViewModel 인스턴스는 메모리에 남습니다. 이런 상황에서 ViewModel 이 액티비티를 참조한다면 액티비티 인스턴스가 메모리에 남게되어 <code>메모리 유실(memory leak)</code> 이 발생할 수 있습니다.</p>
<h2 id="sissaved-instance-state">SIS(Saved Instance State)</h2>
<h3 id="sis-란">SIS 란...?</h3>
<p>: 저장된 인스턴스 상태, 안드로이드 운영체제가 일시적으로 액티비티 외부에 저장하는 데이터이며, Activity.onSaveInstanceState(Bundle) 을 override 하여 <code>SIS</code>에 데이터를 추가할 수 있습니다.</p>
<p>액티비티가 Stopped 될 때 (예, 사용자가 홈 버튼을 누른 후 다른 앱을 실행할 때) 안드로이드 운영체제가 Activity.onSaveInstanceState(Bundle) 을 호출합니다. Activity.onSaveInstanceState(Bundle) 에서는 현재 액티비티의 모든 뷰들의 상태를 Bundle 데이터로 저장합니다. 저장된 데이터는 액티비티가 다시 create 될 때, Activity.onCreate(Bundle?) 에서 전달받습니다. Bundle 객체에 저장된 뷰들의 상태 데이터들을 사용해서 액티비티를 다시 생성합니다.</p>
<h3 id="사용-예제-1">사용 예제</h3>
<pre><code class="language-kotlin">private const val KEY_INDEX = &quot;index&quot;

class MainActivity : AppCompatActivity() {
    ...

    ovverride fun onSaveInstanceState(outState: Bundle){
        super.onSaveInstanceState(outState)
        outState.putInt(KEY_INDEX, quizViewModel.currentIndex)
    }

    override fun onCreate(savedInstanceState: Bundle?){
        ...
        val currentIndex = savedInstanceState?.getInt(KEY_INDEX, 0) ?: 0
    }
}</code></pre>
<h3 id="activity-record">activity record</h3>
<p>액티비티(프로세스)의 소멸에도 onSaveInstanceState(Bundle)에 저장된 데이터가 존속할 수 있는 이유는 Bundle 객체가 액티비티의 <code>activity record</code> 로 저장되기 때문입니다. </p>
<h2 id="sis-vs-aac-viewmodel">SIS vs AAC ViewModel</h2>
<p>SIS 에는 크거나 복잡한 객체를 저장하는 것은 피해야 합니다. 즉, UI 상태를 저장하기 위해 필요한 소량의 정보를 저장할 때는 SIS 를 사용하고, 장치의 구성 변경이 생겨 UI 에 넣는 데 필요한 많은 데이터에 빠르고 쉽게 접근하고자 메모리에 케싱할 때는 ViewModel 을 사용합니다. 프로세스가 종료된 후 액티비티 인스턴스가 다시 생성될 때는 SIS 데이터를 사용하고, 그 SIS 데이터를 사용해서 ViewModel 을 설정할 수 있습니다.</p>
<hr>
<h2 id="reference">Reference</h2>
<p>크리스틴 마시캐노, 실무에 바로 적용하는 안드로이드 프로그래밍</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[안드로이드 4대 컴포넌트(구성 요소)]]></title>
            <link>https://velog.io/@hhi-5258/%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-4%EB%8C%80-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8%EA%B5%AC%EC%84%B1-%EC%9A%94%EC%86%8C</link>
            <guid>https://velog.io/@hhi-5258/%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-4%EB%8C%80-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8%EA%B5%AC%EC%84%B1-%EC%9A%94%EC%86%8C</guid>
            <pubDate>Tue, 27 Apr 2021 17:39:35 GMT</pubDate>
            <description><![CDATA[<p><img src="https://images.velog.io/images/hhi-5258/post/644aee3c-1974-4dfc-89e9-8c9fedbd18f8/image.png" alt=""></p>
<blockquote>
<p>안드로이드 어플리케이션은 Activity, Service, Broadcast Receiver, Content Provider 로 구성되어 있습니다. 이 컴포넌트들은 독립된 형태로 존재하며 intent 를 통해 상호작용 합니다.  </p>
</blockquote>
<h2 id="activity">Activity</h2>
<p>액티비티는 UI를 담당하는 컴포넌트입니다. 즉, 사용자에게 보여지는 화면입니다. 어플리케이션은 반드시  1개 이상의 액티비티를 가지고 있어야 합니다.</p>
<h2 id="service">Service</h2>
<p>서비스는 백그라운드에서 실행되는 컴포넌트입니다. 오랫동안 실행되는 작업이나 원격 프로세스를 위한 작업을 수행합니다. 메인 쓰레드에서 실행되므로 서비스에서 별도의 스레드를 생성해서 작업을 처리해야 합니다.</p>
<h2 id="broadcast-receiver">BroadCast Receiver</h2>
<p>브로드캐스트 리시버는 안드로이드 OS로부터 발생하는 이벤트와 정보(BroadCast) 를 핸들링하는 컴포넌트입니다. 브로드캐스트에는 시스템 부팅 완료, 배터리 부족, 언어설정 변경, 문자메세지 수신 등이 있습니다. 10초 이내의 작업만 보증하므로 오랜 시간 동작해야한다면 별도의 쓰레드나 서비스로 처리해야 합니다.
Broadcast Receiver 를 등록하는 방법에는 2가지가 있습니다. AndroidManifest 에 등록하는 정적 등록과 코드상에서 등록하는 동적 등록이 있습니다.</p>
<h2 id="content-provider">Content Provider</h2>
<p>컨텐트 프로바이더는 애플리케이션 사이에서 각 데이터들을 공유할 수 있도록 하는 구성요소입니다. 안드로이드는 기본적으로 주소록, 이미지, 오디오 등 주요 데이터에 대한 내장 Content Provider를 제공합니다. 
<img src="https://images.velog.io/images/hhi-5258/post/b8f06f11-5bf1-4f83-9886-84c379100276/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-04-28%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%202.36.03.png" alt="">
위의 그림처럼 외부 어플리케이션에서 local DB 의 데이터가 필요한 경우, 외부 어플리케이션은 Content Resolver 를 통해 Uri 를 어플리케이션에 보내고, 해당 어플리케이션의 Content Provider 가 Uri 를 해석하여 필요한 DB 작업을 합니다. 
컨텐트 프로바이더는 기본적인 CRUD(Create, Read, Update, Delete) 연산이 가능합니다.</p>
<hr>
<h2 id="reference">Reference</h2>
<p><a href="https://developer.android.com/guide/components/fundamentals?hl=ko">Android Developers[애플리케이션 기본 항목]</a>
<a href="https://salix97.tistory.com/11">https://salix97.tistory.com/11</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[면접 대비 안드로이드 기초 ]]></title>
            <link>https://velog.io/@hhi-5258/%EB%A9%B4%EC%A0%91-%EB%8C%80%EB%B9%84-%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-%EA%B8%B0%EC%B4%88</link>
            <guid>https://velog.io/@hhi-5258/%EB%A9%B4%EC%A0%91-%EB%8C%80%EB%B9%84-%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-%EA%B8%B0%EC%B4%88</guid>
            <pubDate>Tue, 27 Apr 2021 07:49:12 GMT</pubDate>
            <description><![CDATA[<h3 id="안드로이드-4대-컴포넌트"><a href="https://velog.io/@hhi-5258/%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-4%EB%8C%80-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8%EA%B5%AC%EC%84%B1-%EC%9A%94%EC%86%8C">안드로이드 4대 컴포넌트</a></h3>
<h3 id="activity-의-생명주기">Activity 의 생명주기</h3>
<p><img src="https://images.velog.io/images/hhi-5258/post/db194091-383c-48bf-9502-edef79fd03c1/image.png" alt="">
기존 액티비티에서 전화가 오거나 문자가 오는 등, 액티비티가 사용자에게 보이긴 하지만 포커스가 없는 경우 onPause 가 호출됩니다. 액티비티가 사용자에게 전혀 보이지 않는 경우 onStop 이 호출됩니다. 
다이얼로그를 띄운 경우, 다이얼로그는 액티비티의 일부이므로 onPause 가 호출되지 않습니다.</p>
<h3 id="fragment-간의-데이터-공유">Fragment 간의 데이터 공유</h3>
<h3 id="안드로이드의-context">안드로이드의 Context</h3>
<h3 id="ui-thread--worker-thread--handler--message-queue">UI Thread / Worker Thread / Handler / Message Queue</h3>
<h3 id="비동기-처리를-위한-방법asynctask-rxjava-등">비동기 처리를 위한 방법(AsyncTask, Rxjava 등)</h3>
<h3 id="커스텀뷰-개발-관련-지식-꼭-신경써야하는-처리-onmeasure-onlayout">커스텀뷰 개발 관련 지식( 꼭 신경써야하는 처리, onMeasure, onLayout)</h3>
<h3 id="안드로이드에서-image를-다루기-위한-방법">안드로이드에서 Image를 다루기 위한 방법</h3>
<h3 id="recyclerview-와-viewholder-패턴">RecyclerView 와 ViewHolder 패턴</h3>
<h3 id="intent-intentfilter-와-intentservice">Intent, IntentFilter 와 IntentService</h3>
<h3 id="anr-overdraw-등의-문제와-그것을-디버깅하는-개발자-도구">ANR, OverDraw 등의 문제와 그것을 디버깅하는 개발자 도구</h3>
]]></description>
        </item>
        <item>
            <title><![CDATA[옵저버 패턴, disposable]]></title>
            <link>https://velog.io/@hhi-5258/%EC%98%B5%EC%A0%80%EB%B2%84-%ED%8C%A8%ED%84%B4-disposable</link>
            <guid>https://velog.io/@hhi-5258/%EC%98%B5%EC%A0%80%EB%B2%84-%ED%8C%A8%ED%84%B4-disposable</guid>
            <pubDate>Tue, 27 Apr 2021 07:28:45 GMT</pubDate>
            <description><![CDATA[<h2 id="옵저버-패턴observer-pattern">옵저버 패턴(Observer Pattern)</h2>
<p> 옵저버 패턴은 객체(subject)의 상태 변화를 관찰하는 관찰자들, 즉 옵저버(observer)들의 목록을 객체에 등록하여 상태 변화가 있을 때마다 메서드 등을 통해 객체가 각 옵저버에게 변화를 알리는(notify) 디자인 패턴입니다. 
옵저버 패턴에서 객체(subject)는 옵저버에 대한 정보가 없습니다. 오직 옵저버가 특정 인터페이스(Interface)를 구현한다는 것 만 알고 있습니다. 즉, 옵저버가 무슨 동작을 하는지 모른다는 것입니다. 게다가 옵저버는 언제든지 새로 추가되거나 제거될 수 있으며 새로운 형식의 옵저버를 추가할 때에도 주제에 전혀 영향을 주지 않습니다. 이러한 관계를 <code>느슨한 결합(Loose coupling)</code>이라고 합니다.</p>
<h3 id="예제">예제</h3>
<pre><code class="language-kotlin">typealias Observer&lt;T&gt; = (event: T) -&gt; Unit
class EventSource&lt;T&gt; {
    // observer 객체들을 담는 리스트
    private val observers = mutableListOf&lt;Observer&lt;T&gt;&gt;()
    // observer 리스트에 observer 추가
    fun addObserver(observer: Observer&lt;T&gt;) {
        observers.add(observer)
    }
    // observer 들에게 이벤트 알림
    fun notify(event: T) {
        observers.forEach {
            it(event)
        }
    }
}

fun main() {
    val eventSource = EventSource&lt;String&gt;()
    eventSource.addObserver { println(&quot;첫번째 옵저버: $it&quot;) }
    eventSource.addObserver { println(&quot;두번째 옵저버: $it&quot;) }
    eventSource.notify(&quot;Hello&quot;)
    eventSource.notify(&quot;Hi&quot;)
}</code></pre>
<p>👉 결과</p>
<pre><code>첫번째 옵저버: Hello
두번째 옵저버: Hello
첫번째 옵저버: Hi
두번째 옵저버: Hi</code></pre><h2 id="rxjava-의-특징">Rxjava 의 특징</h2>
<p>Rxjava에는 크게 3가지의 역할이 있습니다.</p>
<ul>
<li>생산자 -&gt; 데이터를 생산하여 전달</li>
<li>소비자 -&gt; 데이터를 받아서 처리</li>
<li>연산자 -&gt; 데이터를 중간에서 가공(생산, 변환, 조합, 거름) 처리</li>
</ul>
<p>Rxjava는 소비자(Subscriber/Observer)가 생산자(Observable/Flowable/Completable/Maybe/Single)를 소비(subscribe)하는 형태로 만들어져 있습니다.</p>
<p>간단히 말하면, 생산자는 데이터를 생산하고 통지하는 클래스이며, 소비자는 통지된 데이터를 전달받아 이 데이터를 처리합니다.</p>
<pre><code class="language-kotlin">fun main() {
    Observable // 생산자
        .just(0,1,2,3) // 생산 연산자
        .map { it * 2 } // 변경 연산자
        .subscribe { println(it) } // 소비자
}</code></pre>
<h2 id="observable-의-소비자-추가-방법">Observable 의 소비자 추가 방법</h2>
<h3 id="observer-방식">Observer 방식</h3>
<p>observer interface 를 구현한 객체를 subscribe 해서 소비자를 추가합니다.
return type 은 Unit 입니다.</p>
<pre><code class="language-kotlin">val observer = object : Observer&lt;Int&gt; {
        override fun onSubscribe(d: Disposable?) {
            // Observable이 데이터 전달할 준비가 되었을 때,
            // 작업 취소를 위한 Disposable를 여기서 받음
        }

        override fun onNext(t: Int?) {
            // Observable이 데이터를 전달할 때 호출
        }

        override fun onError(e: Throwable?) {
            // Observable이 에러를 전달할 때 호출. Error시 Complete없이 종료 된다.
        }

        override fun onComplete() {
            // Observable이 완료된 경우 호출
        }
    }
    Observable.just(1, 2, 3, 4).subscribe(observer)</code></pre>
<h3 id="consumer-방식">Consumer 방식</h3>
<p>각각의 Consumer를 subscribe해서 소비자를 추가합니다.
Consumer는 메소드 한개짜리 자바 인터페이스이므로 SAM을 통해 람다로 표현할 수 있습니다.
subscribe의 return type은 Disposable 입니다.</p>
<pre><code class="language-kotlin">val disposable = Observable.just(1, 2, 3, 4).subscribe(
        { println(&quot;onNext: $it&quot;) }, //onNext
        { println(&quot;onError: $it&quot;) }, //onError
        { println(&quot;onComplete&quot;) } //onComplete
    )</code></pre>
<h2 id="disposable">Disposable</h2>
<p>유한한 아이템을 발행하는 observable 의 경우 onComplete() 호출로 안전하게 종료되지만, 무한하게 아이템을 발행하거나 오랫동안 실행되는 observable 의 경우에는 명시적인 폐기(dispose)가 필요합니다. <code>Disposable.dispose()</code> 메소드를 호출하면 아이템 발행을 중단할 수 있습니다.</p>
<pre><code class="language-kotlin">val disposable = Observable.just(1, 2, 3, 4).subscribe(
        { println(&quot;onNext: $it&quot;) }, //onNext
        { println(&quot;onError: $it&quot;) }, //onError
        { println(&quot;onComplete&quot;) } //onComplete
    )
disposable.dispose()</code></pre>
<h2 id="compositedisposable">CompositeDisposable</h2>
<p>CompositeDisposable 은 여러 disposable 들을 저장하는 container 입니다. 여러 disposable 들을 한꺼번에 dispose 할 때 사용합니다. 
<code>add</code>, <code>addAll</code> 함수로 disposable 을 추가하고, <code>clear</code>, <code>dispose</code> 함수로 dispose 처리합니다. </p>
<blockquote>
<p>clear 함수는 CompositeDisposable을 재사용 가능한 반면, dispose 함수는 CompositeDisposable 를 재사용할 수 없습니다. </p>
</blockquote>
<h3 id="compositedisposableclear-예제">compositeDisposable.clear() 예제</h3>
<pre><code class="language-kotlin">val compositeDisposable = CompositeDisposable()
    val disposable1 = Observable.just(1, 2).subscribe { println(it) }
    compositeDisposable.add(disposable1)
    compositeDisposable.clear()

    val disposable2 = Observable.just(3,4).delay(1, TimeUnit.SECONDS).subscribe { println(it) }
    compositeDisposable.add(disposable2)
    Thread.sleep(2000L)</code></pre>
<p> 👉결과</p>
<pre><code>1
2
3
4</code></pre><h3 id="compositedisposabledispose-예제">compositeDisposable.dispose() 예제</h3>
<pre><code class="language-kotlin"> val compositeDisposable = CompositeDisposable()
    val disposable1 = Observable.just(1, 2).subscribe { println(it) }
    compositeDisposable.add(disposable1)
    compositeDisposable.dispose()

    val disposable2 = Observable.just(3,4).delay(1, TimeUnit.SECONDS).subscribe { println(it) }
    compositeDisposable.add(disposable2)
    Thread.sleep(2000L)</code></pre>
<p>👉결과</p>
<pre><code>1
2</code></pre><hr>
<h2 id="reference">Reference</h2>
<p>옥수환, 아키텍처를 알아야 앱 개발이 보인다
<a href="https://luckygg.tistory.com/181">[LuckyGg][Design Pattern] 옵저버 패턴(Observer Pattern) 이야기 #1 (예제 포함)</a>
<a href="https://velog.io/@p4stel-dev/RxJava-%EC%A0%95%EB%A6%AC-1-Rxjava%EB%9E%80-">https://velog.io/@p4stel-dev/RxJava-정리-1-Rxjava란-</a>
<a href="https://velog.io/@cmplxn/RxJava-%EC%9E%85%EB%AC%B8%ED%95%98%EA%B8%B0-Kotlin">https://velog.io/@cmplxn/RxJava-입문하기-Kotlin</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Rxjava란..?]]></title>
            <link>https://velog.io/@hhi-5258/Rxjava%EB%9E%80</link>
            <guid>https://velog.io/@hhi-5258/Rxjava%EB%9E%80</guid>
            <pubDate>Thu, 22 Apr 2021 08:22:01 GMT</pubDate>
            <description><![CDATA[<h2 id="rxjava란">Rxjava란</h2>
<p>Rxjava란 ReactiveX(Reactive Extensions)를 자바로 구현한 라이브러리입니다. 옵저버 패턴, 이터레이터 패턴, 함수형 프로그래밍의 장점과 개념을 접목한 반응형 프로그래밍 기법을 의미합니다. Rxjava는 이벤트 처리 및 비동기 처리의 구성에 최적화된 라이브러리입니다.</p>
<h2 id="rxjava-설정하기">Rxjava 설정하기</h2>
<p>Rxjava를 프로젝트에 설정하려면 모듈 레벨의 build.gradle 에 아래의 dependency를 추가합니다. </p>
<pre><code>implementation &#39;io.reactivex.rxjava3:rxandroid:3.0.0&#39;
implementation &#39;io.reactivex.rxjava3:rxjava:3.0.7&#39;</code></pre><p>RxAndroid 는 Rxjava에 몇 가지 클래스를 추가해 안드로이드 개발을 쉽게 해주는 라이브러리입니다.</p>
<h2 id="명령형-프로그래밍-vs-반응형-프로그래밍">명령형 프로그래밍 vs 반응형 프로그래밍</h2>
<h3 id="명령형-프로그래밍imperative-programming">명령형 프로그래밍(Imperative Programming)</h3>
<p>명령형 프로그래밍은 작성된 코드가 순서대로 실행되는 방식의 프로그래밍입니다. 코드가 순서대로 실행되는 것은 개발자가 작성한 조건문, 반복문 또는 함수 호출 등에 의해 컴파일러가 다른 코드로 이동하는 것을 의미합니다. </p>
<h3 id="반응형-프로그래밍reactive-programming">반응형 프로그래밍(Reactive Programming)</h3>
<p>반응형 프로그래밍이란 데이터의 흐름을 먼저 정의하고 데이터가 변경되었을 때, 연관되는 함수가 알아서 처리하는 것입니다. 즉, 프로그래머가 어떠한 기능을 직접 정해서 실행하는 것이 아닌, 시스템에 이벤트가 발생하였을 때 알아서 처리되는 것입니다.</p>
<p>기존의 명령형 방식을 pull 방식, 반응형 방식을 push 방식이라고도 합니다. 
pull 방식은 데이터를 사용하는 곳(consumer) 에서 데이터를 직접 가져와 사용한다면, push 방식은 데이터의 변화가 발생한 곳에서 새로운 데이터를 consumer 에게 전달합니다.</p>
<h2 id="rxjava의-필요성">Rxjava의 필요성</h2>
<p>Rxjava는 동시성 문제, 다중 이벤트 처리, 백그라운드 스레드 처리 등에 대해 해결책을 제시합니다. 또한, 데이터들을 빠르게 처리하고 이와 동시에 데이터들을 병합, 필터링, 분할 및 변환 확장할 방법을 제시합니다. Rxjava는 기존에 작성한 비지니스 로직에 변경사항이 생겨도 큰 리팩토링 없이 쉽게 수정할 수 있습니다. 그러므로 Rxjava를 사용하면 프로덕션의 안정성을 유지하면서 애플리케이션을 전략적으로 진화시킬 수 있습니다.</p>
<h2 id="마블-다이어그램">마블 다이어그램</h2>
<p>마블 다이어그램은 Rxjava에 대한 설명시 주로 사용됩니다. 데이터의 흐름을 시각화한 도표로 내용을 이해하는데 도움이 됩니다. 
화살표는 타임라인을 의미하고, 화살표에 있는 막대는 데이터의 완료를 의미합니다. 가운데의 박스는 데이터의 변형을 의미하며, 아래의 화살표는 변형 결과의 타임라인 입니다. 엑스표는 에러를 나타냅니다.
<img src="https://images.velog.io/images/hhi-5258/post/133fcd00-d55d-449f-ba26-097dc5a64f38/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-04-22%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%205.18.18.png" alt=""></p>
<hr>
<h2 id="reference">Reference</h2>
<p>옥수환, 아키텍처를 알아야 앱 개발이 보인다
<a href="http://reactivex.io/">http://reactivex.io/</a>
<a href="https://4z7l.github.io/2020/12/01/rxjava-1.html">[Herstroy] Rxjava 이해하기</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[SingleLiveEvent]]></title>
            <link>https://velog.io/@hhi-5258/AAC-SingleLiveEvent</link>
            <guid>https://velog.io/@hhi-5258/AAC-SingleLiveEvent</guid>
            <pubDate>Sun, 04 Apr 2021 02:12:49 GMT</pubDate>
            <description><![CDATA[<h2 id="singleliveevent">SingleLiveEvent</h2>
<p>ViewModel 에서 View 에 이벤트를 전달할 때, 값을 전달하는 경우가 아닌 이벤트가 발생했다는 사실만을 전달하고 싶을 때, 아래의 코드와 같이 Unit 값을 전달하여 이벤트를 감지하도록 했었습니다. </p>
<pre><code class="language-kotlin">    private val _signUpClickEvent = MutableLiveData&lt;Unit&gt;()
    _signUpClickEvent.value = Unit</code></pre>
<p>위의 코드는 비효율적일 뿐만 아니라, LiveData를 이용하기에 View의 재활성화 (start나 resume 상태로 재진입)가 될 경우에도 LiveData가 observe를 호출하여, 불필요한 이벤트까지 일어나는 경우가 있습니다.
이를 방지하기 위해 SingleLiveEvent 를 사용합니다.</p>
<h2 id="구글-샘플-코드">구글 샘플 코드</h2>
<pre><code class="language-kotlin">class SingleLiveEvent&lt;T&gt; : MutableLiveData&lt;T&gt;() {
    private val isPending = AtomicBoolean(false)


    @MainThread
    override fun setValue(value: T?) {
        isPending.set(true)
        super.setValue(value)
    }


    // Observe the internal MutableLiveData
    @MainThread
    override fun observe(owner: LifecycleOwner, observer: Observer&lt;in T&gt;) {
        super.observe(owner, Observer {
            if (isPending.compareAndSet(true, false)) {
                observer.onChanged(it)
            }
        })
    }

    /**
     * Used for cases where T is Void, to make calls cleaner.
     */
    @MainThread
    fun call() {
        value = null
    }
}</code></pre>
<p>SingleLiveEvent라는 MutableLiveData를 상속한 클래스를 만듭니다.
isPending.compareAndSet(true, false)라는 것은, 위의 pending 변수가 true면 if문 내의 로직을 처리하고 false로 바꾼다는 것입니다. setValue를 통해서만 pending값이 true로 바뀌기 때문에, Configuration Changed가 일어나도 pending값은 false이기 때문에 observe가 데이터를 전달하지 않습니다.데이터의 속성을 지정해주지 않아도 call만으로 setValue를 호출 가능합니다.</p>
<h2 id="사용법">사용법</h2>
<pre><code class="language-kotlin">class ListViewModel : ViewModel {
    private val _navigateToDetails = SingleLiveEvent&lt;Any&gt;()

    val navigateToDetails : LiveData&lt;Any&gt;
        get() = _navigateToDetails


    fun userClicksOnButton() {
        _navigateToDetails.call()
    }
}</code></pre>
<hr>
<h2 id="reference">Reference</h2>
<p><a href="https://medium.com/androiddevelopers/livedata-with-snackbar-navigation-and-other-events-the-singleliveevent-case-ac2622673150">[medium]LiveData with SnackBar, Navigation and other events (the SingleLiveEvent case)</a>
<a href="https://www.charlezz.com/?p=1152">[찰스의 안드로이드]SingleLiveEvent로 이벤트 처리하기</a>
<a href="https://zladnrms.tistory.com/146">[치킨과 개발의 상관관계] [Android] SingleLiveEvent의 원리</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Hilt를 사용한 DI(Dependency Injection)]]></title>
            <link>https://velog.io/@hhi-5258/Hilt%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%9C-DIDependency-Injection</link>
            <guid>https://velog.io/@hhi-5258/Hilt%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%9C-DIDependency-Injection</guid>
            <pubDate>Thu, 25 Mar 2021 09:27:09 GMT</pubDate>
            <description><![CDATA[<h2 id="dependency-injection-의존성-주입">Dependency Injection (의존성 주입)</h2>
<p><code>의존성 주입</code> 은 의존 관계에 있는 클래스의 객체를 외부로 부터 생성하여 주입받는 것입니다. 
의존성 주입을 적용한 코드와 적용하지 않은 코드를 비교해 보면 더 잘 이해할 수 있습니다.</p>
<pre><code class="language-kotlin">// 의존성 주입이 없는 코드
class MemoRepository {
    private val db = SQLiteDatabase()

    fun load (id: String) {...}
}

fun main() {
    val repository = MemoRepository()
    repository.load(&quot;8092&quot;)
}</code></pre>
<pre><code class="language-kotlin">// 의존성 주입을 적용한 코드
class MemoRepository(private val db: Database) {

    fun load (id: String) {...}
}

fun main() {
    val db = SQLiteDatabase()
    val repository = MemoRepository(db)
    repository.load(&quot;8092&quot;)
}</code></pre>
<h4 id="의존성-주입의-장단점">의존성 주입의 장단점</h4>
<ul>
<li>장점
리팩토링이 수월하다. 
클래스간의 결합도를 느슨하게 한다.
stub이나 mock 객체를 사용하여 단위 테스트가 용이하다.</li>
<li>단점
러닝커브가 높다
간단한 프로그램을 만들때는 번거롭다.</li>
</ul>
<h2 id="hilt">Hilt</h2>
<p>Dagger Hilt는 2020년 6월 Google에서 오피셜하게 발표한 Android 전용 DI 라이브러리입니다. Hilt는 Dagger2를 기반으로 Android Framework에서 표준적으로 사용되는 DI component와 scope를 기본적으로 제공하여, 초기 DI 환경 구축 비용을 크게 절감시키는 것이 가장 큰 목적입니다.</p>
<h2 id="hilt-annotation">Hilt Annotation</h2>
<h3 id="hiltandroidapp">@HiltAndroidApp</h3>
<p>Hilt 를 사용하는 모든 앱은 @HiltAndroidApp 어노테이션을 포함하는 application 을 생성해야 합니다. @HiltAndroidApp은 Hilt 컴포넌트의 코드 생성과 컴포넌트를 사용하는 Application의 기본 클래스를 생성하게 됩니다.</p>
<pre><code class="language-kotlin">@HiltAndroidApp
class App : Application () {
    override fun onCreate() {
        super.onCreate()
    }
}</code></pre>
<h3 id="androidentrypoint">@AndroidEntryPoint</h3>
<p>Application에서 멤버 주입이 가능하게 설정하고 나면, 다른 안드로이드 클래스들에서도
@AndroidEntryPoint 어노테이션을 사용하여 안드로이드에 DI 컨테이너를 추가하여 멤버 주입을 하는 것이 가능해 집니다. 
@AndroidEntryPoint를 사용할 수 있는 타입은 다음과 같습니다.</p>
<ul>
<li>Activity, Fragment, View, Service, BroadCastReceiver <pre><code class="language-kotlin">@AndroidEntryPoint
class MyActivity : MyBaseActivity() {
  @Inject lateinit var bar: Bar // ApplicationComponent 또는 ActivityComponent으로 부터 의존성이 주입된다.
  override fun onCreate() {
// super.onCreate()에서 의존성 주입이 발생한다.
      super.onCreate()
// Do something with bar ...
  }
}</code></pre>
<h2 id="hilt-module">Hilt Module</h2>
때로 생성자 삽입할 수 없는 상황도 있습니다. 예를 들어 인터페이스를 생성자 삽입할 수 없습니다. 또한 외부 라이브러리의 클래스와 같이 소유하지 않은 유형도 생성자 삽입할 수 없습니다. 이럴 때는 Hilt 모듈을 사용하여 Hilt에 결합 정보를 제공할 수 있습니다.</li>
</ul>
<h3 id="module-installin">@Module, @InstallIn</h3>
<p>모듈은 컴포넌트에 의존성을 제공하는 역할을 합니다. 클래스에 @Module 어노테이션을 붙여 모듈 클래스를 만들 수 있습니다. 모듈 클래스 내에 선언되는 매서드에 @Provides 어노테이션을 붙여 컴파일 타임에 의존성을 제공하는 바인드된 프로바이더를 생성합니다.
간단히 설명하면 @Module 은 의존성을 제공하는 클래스에 붙이고, @Provides 는 의존성을 제공하는 메소드에 붙인다고 생각하면 됩니다.
Hilt의 모듈은 @InstallIn이라는 추가적인 어노테이션을 갖습니다. @InstallIn은 Hilt의 표준 컴포넌트들 중 어떤 컴포넌트에 모듈을 설치할지 결정합니다.</p>
<pre><code class="language-kotlin">@Module
@InstallIn(ApplicationComponent.class) // 생성되는 ApplicationComponent에 FooModule을 설치함
        public final class FooModule {
    @Provides
    static Bar provideBar() {...}
}</code></pre>
<h3 id="binds를-사용하여-인터페이스-인스턴스-삽입">@Binds를 사용하여 인터페이스 인스턴스 삽입</h3>
<p>인터페이스라면 이 인터페이스를 생성자 삽입할 수 없습니다. 대신 Hilt 모듈 내에 @Binds로 어노테이션이 지정된 추상 함수를 생성하여 Hilt에 결합 정보를 제공합니다.
@Binds 어노테이션은 인터페이스의 인스턴스를 제공해야 할 때 사용할 구현을 Hilt에 알려줍니다.</p>
<h3 id="provides를-사용하여-인스턴스-삽입">@Provides를 사용하여 인스턴스 삽입</h3>
<p>인터페이스가 유형을 생성자 삽입할 수 없는 유일한 경우는 아닙니다. 클래스가 외부 라이브러리에서 제공되므로 클래스를 소유하지 않은 경우(Retrofit, OkHttpClient 또는 Room 데이터베이스와 같은 클래스) 또는 빌더 패턴으로 인스턴스를 생성해야 하는 경우에도 생성자 삽입이 불가능합니다.
클래스를 직접 소유하지 않으면 Hilt 모듈 내에 함수를 생성하고 이 함수에 @Provides 어노테이션을 지정하여 이 유형의 인스턴스를 제공하는 방법을 Hilt에 알릴 수 있습니다.</p>
<pre><code class="language-kotlin">@Module
@InstallIn(ActivityComponent::class)
object AnalyticsModule {

  @Provides
  fun provideAnalyticsService(
    // Potential dependencies of this type
  ): AnalyticsService {
      return Retrofit.Builder()
               .baseUrl(&quot;https://example.com&quot;)
               .build()
               .create(AnalyticsService::class.java)
  }
}</code></pre>
<h2 id="hilt-component">Hilt component</h2>
<p>기존의 Dagger와 다르게 Hilt사용자는 Dagger 컴포넌트를 직접적으로 정의하거나 인스턴스화 할 필요가 없어졌습니다. Hilt는 안드로이드 Application의 다양한 생명주기에 자동으로 통합되는 내장 컴포넌트 세트를 해당 스코프 어노테이션과 함께 제공합니다. 아래에 있는 다이어그램은 표준 Hilt 컴포넌트 계층을 보여주고 있습니다다. 각 컴포넌트에 위에달린 어노테이션은 컴포넌트 바인딩의 생명주기를 지정하는 데 사용됩니다. 각 컴포넌트 아래에 있는 화살표는 하위 컴포넌트를 가르키고 있습니다. 하위 컴포넌트의 바인딩은 상위 컴포넌트의 바인딩이 가지고 있는 의존성들을 가질 수 있습니다.</p>
<p><img src="https://images.velog.io/images/hhi-5258/post/fcd8b8a8-0108-4e78-8923-9aabcb444ffb/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-03-25%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%206.00.08.png" alt=""></p>
<p>@InstallIn이 달린 모듈의 바인딩에 스코프가 지정될 때는 반드시 모듈이 설치되는 컴포넌트의 스코프와 일치해야 합니다. 예를 들면, @InstallIn(ActivityComponent.class) 모듈은 @ActivityScoped 만 사용할 수 있습니다.</p>
<h3 id="component-수명">Component 수명</h3>
<p>컴포넌트의 수명은 안드로이드 클래스에 대응하는 인스턴스 생성과 소멸을 따라갑니다. 컴포넌트가 생성되고 종료될 때, 해당 스코프 어노테이션이 지정된 바인딩 또한 수명을 함께합니다. 컴포넌트 수명은 멤버 주입된 값들이 사용될 수 있는 시기를 나타냅니다.
다음 표는 스코프 어노테이션과 각 컴포넌트에 맞는 수명을 목록으로 보여주고 있습니다.
<img src="https://images.velog.io/images/hhi-5258/post/9e0aec29-31dc-42eb-993d-6ba26eaff430/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-03-25%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%206.17.21.png" alt=""></p>
<pre><code class="language-kotlin">@InstallIn(ApplicationComponent::class)
@Module
class RetrofitCreate {

    @Provides
    @Singleton
    fun createRetrofit(): Retrofit {...}
    }
}</code></pre>
<h2 id="hilt-androidx-extensions">Hilt AndroidX Extensions</h2>
<p>Hilt는 jetpack 라이브러리 클래스를 위해 extensions 를 제공합니다. 지원하는 컴포넌트로 ViewModel, WorkManager 가 있습니다. </p>
<h3 id="viewmodelinject">@ViewModelInject</h3>
<pre><code class="language-kotlin">class MainViewModel @ViewModelInject constructor(
    private val loginRepositoryImpl: LoginRepositoryImpl
) : BaseViewModel() {...}</code></pre>
<hr>
<h2 id="reference">Reference</h2>
<p><a href="https://developer.android.com/training/dependency-injection/hilt-android?hl=ko">[Android Developers] Hilt를 사용한 종속 항목 삽입</a>
<a href="https://youtu.be/gkUCs6YWzEY">[드로이드나이츠 2020] 옥수환 - Hilt와 함께 하는 안드로이드 의존성 주입</a>
<a href="https://hyperconnect.github.io/2020/07/28/android-dagger-hilt.html">[하이퍼커넥트 기술블로그] Dagger Hilt로 안드로이드 의존성 주입 시작하기</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Theme 을 이용한 Splash 화면 ]]></title>
            <link>https://velog.io/@hhi-5258/Theme-%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%9C-Splash-%ED%99%94%EB%A9%B4</link>
            <guid>https://velog.io/@hhi-5258/Theme-%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%9C-Splash-%ED%99%94%EB%A9%B4</guid>
            <pubDate>Fri, 19 Mar 2021 06:42:42 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>메인액티비티가 실행되기 전까지 로딩화면을 보여주는 splash 화면을 효율적으로 적용해 보았습니다. 
메인액티비티에 Splash Theme 을 적용하고, 메인액티비티가 생성되면 Theme 을 전체 프로젝트의 AppTheme 으로 변경하였습니다.</p>
</blockquote>
<h2 id="background_splahsxml-생성">background_splahs.xml 생성</h2>
<p>drawable 폴더 내에 splash 화면을 생성합니다.</p>
<pre><code>&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;layer-list xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;&gt;
    &lt;item&gt;
        &lt;bitmap android:src=&quot;@drawable/intropage&quot;
            android:gravity=&quot;center&quot;/&gt;
    &lt;/item&gt;
&lt;/layer-list&gt;</code></pre><h2 id="splashtheme-생성">SplashTheme 생성</h2>
<p>values 폴더 내 themes 파일에 splash 를 위한 style 을 생성합니다.</p>
<pre><code>&lt;!--Splash theme --&gt;
    &lt;style name=&quot;SplashTheme&quot; parent=&quot;Theme.MaterialComponents.DayNight.NoActionBar&quot;&gt;
        &lt;item name=&quot;android:windowBackground&quot;&gt;@drawable/background_splash&lt;/item&gt;
    &lt;/style&gt;</code></pre><h2 id="mainactivity-에-theme-적용">MainActivity 에 Theme 적용</h2>
<p>AndroidManifest 파일 내에 MainActivity 에 theme 을 적용합니다.</p>
<pre><code>&lt;activity android:name=&quot;.view.main.MainActivity&quot;
            android:theme=&quot;@style/SplashTheme&quot;&gt;</code></pre><h2 id="mainactivity-생성-후-원래-theme-으로-변경">MainActivity 생성 후 원래 Theme 으로 변경</h2>
<p>MainActivity 의 onCreate 내에 setTheme 함수를 이용하여 원래의 theme 으로 변경시켜 줍니다. <code>super.onCreate(savedInstance)</code> 가 호출되기 전에 setTheme 함수를 적용합니다.</p>
<pre><code class="language-kotlin"> override fun onCreate(savedInstanceState: Bundle?) {
        setTheme(R.style.TripProjectTheme)
        super.onCreate(savedInstanceState)
    }</code></pre>
<hr>
<h2 id="reference">Reference</h2>
<p>[<a href="https://velog.io/@pish11010/Android-Splash-Screen-%EA%B5%AC%ED%98%84%5D">https://velog.io/@pish11010/Android-Splash-Screen-구현]</a>
(<a href="https://velog.io/@pish11010/Android-Splash-Screen-%EA%B5%AC%ED%98%84">https://velog.io/@pish11010/Android-Splash-Screen-구현</a>)
[<a href="https://holika.tistory.com/entry/%EB%82%B4-%EB%A7%98%EB%8C%80%EB%A1%9C-%EC%A0%95%EB%A6%AC%ED%95%9C-%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-%EC%8A%A4%ED%94%8C%EB%9E%98%EC%8B%9CSplash-%ED%99%94%EB%A9%B4%EC%9D%80-%EC%96%B4%EB%96%BB%EA%B2%8C-%EB%A7%8C%EB%93%A4%EC%96%B4%EC%95%BC-%ED%9A%A8%EC%9C%A8%EC%A0%81%EC%9C%BC%EB%A1%9C-%ED%99%9C%EC%9A%A9%ED%95%A0-%EC%88%98-%EC%9E%88%EC%9D%84%EA%B9%8C%5D">https://holika.tistory.com/entry/내-맘대로-정리한-안드로이드-스플래시Splash-화면은-어떻게-만들어야-효율적으로-활용할-수-있을까]</a>
(<a href="https://holika.tistory.com/entry/%EB%82%B4-%EB%A7%98%EB%8C%80%EB%A1%9C-%EC%A0%95%EB%A6%AC%ED%95%9C-%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-%EC%8A%A4%ED%94%8C%EB%9E%98%EC%8B%9CSplash-%ED%99%94%EB%A9%B4%EC%9D%80-%EC%96%B4%EB%96%BB%EA%B2%8C-%EB%A7%8C%EB%93%A4%EC%96%B4%EC%95%BC-%ED%9A%A8%EC%9C%A8%EC%A0%81%EC%9C%BC%EB%A1%9C-%ED%99%9C%EC%9A%A9%ED%95%A0-%EC%88%98-%EC%9E%88%EC%9D%84%EA%B9%8C">https://holika.tistory.com/entry/내-맘대로-정리한-안드로이드-스플래시Splash-화면은-어떻게-만들어야-효율적으로-활용할-수-있을까</a>)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Android KTX]]></title>
            <link>https://velog.io/@hhi-5258/Android-KTX</link>
            <guid>https://velog.io/@hhi-5258/Android-KTX</guid>
            <pubDate>Sat, 13 Mar 2021 09:01:43 GMT</pubDate>
            <description><![CDATA[<h2 id="android-ktx">Android KTX</h2>
<p><code>Android KTX</code> 는 android 를 위한 코틀린(Kotlin) 개발용 확장(Extensions) 라이브러리입니다. Android KTX 는 안드로이드 프레임워크와 서포트 라이브러리를 모두 지원하여 코틀린 코드를 더 자연스럽게 사용할 수 있도록 합니다. 
Android KTX 는 모듈로 구성됩니다. 필요한 기능을 담은 모듈을 추가하여 사용할 수 있습니다.</p>
<h2 id="android-ktx-사용">Android KTX 사용</h2>
<p>android KTX 를 사용하려면 프로젝트 수준의 build.gradle 파일에 아래의 코드를 추가해야 합니다.</p>
<pre><code>repositories {
    google()
}</code></pre><h2 id="android-ktx-모듈별-사용-예">Android KTX 모듈별 사용 예</h2>
<ul>
<li>Core KTX
Core KTX 모듈은 Android 프레임워크의 일부인 일반 라이브러리에 확장 프로그램을 제공합니다. 이 모듈을 포함하려면 app 수준의 build.gradle 파일에 다음을 추가합니다.<pre><code>dependencies {
  implementation &quot;androidx.core:core-ktx:1.3.2&quot;
}</code></pre>Core KTX 모듈을 사용하면 Sharedpreference 를 더 쉽게 편집할 수 있습니다.<pre><code class="language-kotlin">      preference.edit().putString(key, value).apply()</code></pre>
위의 코드를 아래와 같이 쓸 수 있습니다.<pre><code class="language-kotlin">      preference.edit { putString(key, value) }</code></pre>
</li>
<li>Activity KTX
Activity KTX 는 여러 확장 프로그램을 제공하여 Activity API 를 단순화합니다. Activity KTX 모듈을 포함하려면 다음을 추가합니다.<pre><code>dependencies {
  implementation &quot;androidx.activity:activity-ktx:1.2.0&quot;
}</code></pre>Activity KTX 를 사용하면 <code>ViewModel</code> 을 간단하게 할당할 수 있습니다.<pre><code class="language-kotlin">  private lateinit var vm: MainViewModel
  vm = ViewModelProvider(this).get(MainViewModel::class.java)
</code></pre>
</li>
</ul>
<pre><code>위의 코드를 아래와 같이 쓸 수 있습니다.
```kotlin
    private val vm: MainViewModel by viewModels()
```
그 외에도 아래와 같은 KTX 모듈들이 있습니다.
- Collection KTX
- Fragment KTX
- LiveData KTX
- LifeCycle KTX
- Room KTX 
...

---
## Reference
[Android Developer](https://developer.android.com/kotlin/ktx?hl=ko#core)
[Google Developers](https://developers-kr.googleblog.com/2018/02/introducing-android-ktx-even-sweeter-kotlin-development-for-android.html)
[AndroidX Tech](https://androidx.tech/artifacts/activity/activity-ktx/)</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[Application Context 가져오기]]></title>
            <link>https://velog.io/@hhi-5258/Application-Context-%EA%B0%80%EC%A0%B8%EC%98%A4%EA%B8%B0</link>
            <guid>https://velog.io/@hhi-5258/Application-Context-%EA%B0%80%EC%A0%B8%EC%98%A4%EA%B8%B0</guid>
            <pubDate>Sat, 13 Mar 2021 07:59:50 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>application context 를 전역에서 불러오기 위해 application context를 singleton 으로 생성해 줍니다. </p>
</blockquote>
<h2 id="manifest-추가">Manifest 추가</h2>
<pre><code>&lt;application
        android:name=&quot;.utils.App&quot;&gt;
        ...
    &lt;/application&gt;</code></pre><h2 id="app-클래스-생성">App 클래스 생성</h2>
<p>application Context 를 singleton 으로 생성합니다.</p>
<pre><code class="language-kotlin">class App : Application () {
    init {
        instance = this
    }
    companion object {
        private var instance: App? = null
        fun applicationContext() : Context {
            return instance!!.applicationContext
        }
    }
}</code></pre>
<p>아래와 같은 방식으로 context 를 불러 사용합니다.</p>
<pre><code class="language-kotlin">App.applicationContext()</code></pre>
<hr>
<h2 id="reference">Reference</h2>
<p><a href="https://ddangeun.tistory.com/78">https://ddangeun.tistory.com/78</a>
<a href="https://namget.tistory.com/entry/%EC%BD%94%ED%8B%80%EB%A6%B0-mvvm%ED%8C%A8%ED%84%B4-%EC%86%8D-application-context-%EA%B0%80%EC%A0%B8%EC%98%A4%EA%B8%B0">남갯,YTS의 개발,일상블로그</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[네이버 아이디 로그인 ]]></title>
            <link>https://velog.io/@hhi-5258/%EB%84%A4%EC%9D%B4%EB%B2%84-%EC%95%84%EC%9D%B4%EB%94%94-%EB%A1%9C%EA%B7%B8%EC%9D%B8</link>
            <guid>https://velog.io/@hhi-5258/%EB%84%A4%EC%9D%B4%EB%B2%84-%EC%95%84%EC%9D%B4%EB%94%94-%EB%A1%9C%EA%B7%B8%EC%9D%B8</guid>
            <pubDate>Sat, 13 Mar 2021 06:16:17 GMT</pubDate>
            <description><![CDATA[<h2 id="네이버-api-key-생성">네이버 API KEY 생성</h2>
<p>네이버 developer 사이트에 들어가서 어플리케이션을 등록하고 api key를 생성합니다. 생성한 api key 는 보안을 위해 <code>key.properties</code> 파일을 생성하여 저장합니다. 앱 단위의 build.gradle 파일에 아래의 코드를 추가하여 key.properties 파일의 값들을 불러옵니다.</p>
<pre><code>def keys = new Properties()
file(&quot;../key.properties&quot;).withInputStream {
    stream -&gt; keys.load(stream)
}

defaultConfig {
        ...
        buildConfigField(&quot;String&quot;, &quot;NAVER_CLIENT_ID&quot;, keys.NAVER_CLIENT_ID)
        buildConfigField(&quot;String&quot;, &quot;NAVER_CLIENT_SECRET&quot;, keys.NAVER_CLIENT_SECRET)
    }</code></pre><p>key.properties 파일을 gitignore 파일에 추가합니다.</p>
<h2 id="환경-설정">환경 설정</h2>
<p>앱 모듈의 build.gradle 에 dependency를 추가해 줍니다.</p>
<pre><code>implementation &#39;com.naver.nid:naveridlogin-android-sdk:4.2.6&#39;</code></pre><h2 id="버튼-추가">버튼 추가</h2>
<p>OAuthLoginButton 객체로 버튼을 추가합니다.</p>
<pre><code>&lt;com.nhn.android.naverlogin.ui.view.OAuthLoginButton
            android:id=&quot;@+id/main_login_naver&quot;
            android:layout_width=&quot;250dp&quot;
            android:layout_height=&quot;53dp&quot;
            /&gt;</code></pre><h2 id="startoauthloginactivity-메소드를-이용한-로그인">startOAuthLoginActivity() 메소드를 이용한 로그인</h2>
<pre><code class="language-kotlin">    // 네이버 아이디 로그인 인스턴스 초기화 
            mOAuthLoginInstance = OAuthLogin.getInstance()
            mOAuthLoginInstance.init(
                App.context,
                BuildConfig.NAVER_CLIENT_ID,
                BuildConfig.NAVER_CLIENT_SECRET,
                R.string.app_name.toString()
            )

    // OAuthLoginHandler 로 accessToken 가져오기
            mOAuthLoginInstance.startOauthLoginActivity(this, @SuppressLint(&quot;HandlerLeak&quot;)
            object : OAuthLoginHandler() {
                override fun run(success: Boolean) {
                    if (success) {
                        val accessToken: String = mOAuthLoginInstance.getAccessToken(baseContext)
                        Toast.makeText(baseContext, accessToken, Toast.LENGTH_SHORT).show()
                    } else {
                        val errorCode: String =
                            mOAuthLoginInstance.getLastErrorCode(App.context).code
                        val errorDesc = mOAuthLoginInstance.getLastErrorDesc(App.context)

                        Toast.makeText(
                            baseContext,
                            &quot;errorCode: $errorCode, errorDesc: $errorDesc&quot;,
                            Toast.LENGTH_SHORT
                        ).show()
                    }
                }
            })</code></pre>
<hr>
<h2 id="reference">Reference</h2>
<p><a href="https://developers.naver.com/docs/login/android/">Naver Developers</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[DataBinding, LiveData, MVVM 적용해보기]]></title>
            <link>https://velog.io/@hhi-5258/DataBinding-LiveData-MVVM-%EC%A0%81%EC%9A%A9%ED%95%B4%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@hhi-5258/DataBinding-LiveData-MVVM-%EC%A0%81%EC%9A%A9%ED%95%B4%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Thu, 11 Mar 2021 10:34:53 GMT</pubDate>
            <description><![CDATA[<h2 id="환경설정">환경설정</h2>
<p>data binding 을 위해 앱 모듈의 build.gradle 파일을 수정합니다.</p>
<pre><code>dataBinding {
        enabled = true
    }</code></pre><p>xml 파일에 <code>&lt;layout&gt;</code> 태그를 추가하여 databinding 을 적용합니다.</p>
<pre><code>&lt;layout ...&gt;
    &lt;data&gt;
        ...
    &lt;/data&gt;

    &lt;androidx.constraintlayout.widget.ConstraintLayout
        ...
    &lt;/androidx.constraintlayout.widget.ConstraintLayout&gt;
&lt;/layout&gt;</code></pre><p>해당 액티비티에서 binding 클래스 객체를 생성해 주고, LiveData 를 위한 lifeCycleOwner 도 설정합니다.</p>
<pre><code class="language-kotlin">class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val binding : ActivityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main)
        binding.lifecycleOwner = this
    }
}</code></pre>
<h2 id="viewmodel-생성">ViewModel 생성</h2>
<p>View 에서 쓰이는 속성들을 담은 ViewModel 을 생성해 줍니다. 화면전환과 같은 변화에도 데이터가 유지될 수 있도록 AAC ViewModel 도 적용시켜 줍니다. 
ViewModel 내에서만 data 를 변경할 수 있도록 private val 로 data 를 선언합니다. 이벤트(input)가 감지되면 ViewModel 내에서 data를 변화시키는 함수를 생성합니다. View 에서 LiveData 를 Observe 하고 있으니 LiveData 에 변화가 있으면 View 에서 이를 파악하고 View 를 갱신합니다. </p>
<pre><code class="language-kotlin">import androidx.lifecycle.ViewModel

class MainViewModel : ViewModel() {
    private val _signUpEvent = MutableLiveData&lt;Unit&gt;()
    val signUpEvent : LiveData&lt;Unit&gt; = _signUpEvent

    private val _loginEvent = MutableLiveData&lt;Unit&gt;()
    val loginEvent : LiveData&lt;Unit&gt; = _loginEvent

    fun login() {
        _signUpEvent.value = Unit
    }

    fun signUp() {
        _loginEvent.value = Unit
    }
}</code></pre>
<h2 id="databinding">DataBinding</h2>
<p>ViewModel 변수를 선언해주고, 변화가 감지될 이벤트에 ViewModel의 속성을 DataBinding 해줍니다.</p>
<pre><code>    &lt;data&gt;
        &lt;variable
            name=&quot;vm&quot;
            type=&quot;com.hhi.tripproject.viewmodel.MainViewModel&quot; /&gt;
    &lt;/data&gt;

    &lt;TextView
            ...
            android:onClick=&quot;@{()-&gt;vm.signUp()}&quot;
     /&gt;</code></pre><p>액티비티에서 ViewModel 을 바인딩 해줍니다.</p>
<pre><code class="language-kotlin">override fun onCreate(savedInstanceState: Bundle?) {
        ...

        vm = MainViewModel()
        binding.vm = vm
    }</code></pre>
<p>binding 된 LiveData를 Observe 하고, 변화된 data 에 따라 View 가 갱신되도록 로직을 설정해 줍니다.</p>
<pre><code class="language-kotlin">vm.signUp.observe(this, Observer {
            startActivity(Intent(this, SignUpActivity::class.java))
        })

        vm.login.observe(this, Observer {
            startActivity(Intent(this, LoginActivity::class.java))
        })</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Design Pattern]]></title>
            <link>https://velog.io/@hhi-5258/Design-Pattern</link>
            <guid>https://velog.io/@hhi-5258/Design-Pattern</guid>
            <pubDate>Thu, 11 Mar 2021 08:06:03 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>불필요한 코드의 중복을 방지하고, 코드를 효율적으로 재활용하기 위해 많은 디자인 패턴들이 사용됩니다. 그 중 많이 사용되고 있는 디자인 패턴들에 대해 찾아 봤습니다.</p>
</blockquote>
<h2 id="mvc">MVC</h2>
<p><img src="https://images.velog.io/images/hhi-5258/post/52bde0c2-4cef-42eb-9526-98f847e68966/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-03-11%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%202.26.15.png" alt="">
MVC 패턴은 사용자에게 보여지는 UI를 담당하는 <code>View</code>, input 을 받아 처리하는 <code>Controller</code>, data를 담고 있는 <code>Model</code> 로 이루어져 있습니다. <code>controller</code> 에서 event를 받으면, <code>Model</code> 에서 data 를 가져와 처리한 후 <code>View</code> 에 결과를 뿌려줍니다. 
데이터의 입력과 처리를 모두 <code>Controller</code> 가 담당하여 <code>Controller</code> 가 무거워 졌습니다. 이를 개선한 것이 MVP 패턴입니다.</p>
<h2 id="mvp">MVP</h2>
<p><img src="https://images.velog.io/images/hhi-5258/post/caf4be69-fd44-4557-aae4-e70fa69f638c/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-03-11%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%202.26.32.png" alt="">
MVP 패턴은 사용자에게 보여지는 UI와 입력을 담당하는 <code>View</code>, 데이터를 처리하는 <code>Presenter</code>, data를 담고 있는 <code>Model</code> 로 이루어져 있습니다.
<code>View</code> 에서 이벤트를 받으면 그 사실을 <code>Presenter</code> 에게 전달합니다. <code>Presenter</code> 가 처리한 후 다시 결과를 <code>View</code>에게 전달합니다. <code>Presenter</code> 는 데이터의 처리만을 담당하게 되었습니다. 
각 View에 대한 입력을  처리하기 위해 View 와 Presenter는 1대 1의 관계가 요구됩니다. 비슷한 동작을 하는 View 일지라도, Presenter 를 재활용할 수 없습니다. 이를 개선한 것이 MVVM 패턴입니다.</p>
<h2 id="mvvm">MVVM</h2>
<p><img src="https://images.velog.io/images/hhi-5258/post/07a9e45c-3415-4da4-b222-53d250d92786/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-03-11%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%202.26.43.png" alt="">
MVVM 패턴은 UI를 담당하는 <code>View</code>, 데이터를 담고 있는 <code>Model</code>, 비지니스 로직이 구현되어 있는 <code>ViewModel</code> 로 이루어져 있습니다.</p>
<p>MVVM 패턴의 기본은 View 와 ViewModel 을 직접 연결하지 않는 것입니다. 그렇다면 둘은 어떻게 변화를 파악할 수 있을까요? <a href="https://velog.io/@hhi-5258/Data-Binding">Data Binding</a> 을 사용하면 해결할 수 있습니다. 
View 는 ViewModel 을 선택해 Data Binding 합니다. View 는 ViewModel 을 직접 참조하지 않고, 관찰(Observe) 하다가 변화가 생기면 View를 갱신합니다. ViewModel 은 View가 Data Binding 할 수 있는 속성과 명령들로 이루어져 있습니다.</p>
<p>View와 ViewModel, View 와 Model 사이의 의존성이 없으므로 테스트가 용이 합니다.</p>
<h2 id="aac-의-viewmodel-과-mvvm-의-viewmodel">AAC 의 ViewModel 과 MVVM 의 ViewModel</h2>
<p><a href="https://velog.io/@hhi-5258/ViewModel">AAC 의 ViewModel</a> 은 액티비티, 프래그먼트의 lifecycle 과 독립된 환경 변화에도 유지되는 lifecycle 을 가지는 클래스를 의미하고, MVVM 의 ViewModel 은 View 와 Model 사이에서 바인딩하고, 로직을 처리하는 클래스를 의미합니다.
MVVM 의 ViewModel 에서 AAC 의 ViewModel 을 사용할 수도 있고, 사용하지 않아도 MVVM 의 ViewModel은 구현 가능합니다.</p>
<hr>
<h2 id="reference">Reference</h2>
<p><a href="https://www.youtube.com/watch?v=bjVAVm3t5cQ">https://www.youtube.com/watch?v=bjVAVm3t5cQ</a>
<a href="https://github.com/KRMKGOLD/Android-MVVMSample">https://github.com/KRMKGOLD/Android-MVVMSample</a>
<a href="https://velog.io/@jojo_devstory/%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-%ED%8C%A8%ED%84%B4-MVVM%EC%9D%B4-%EB%AD%98%EA%B9%8C">https://velog.io/@jojo_devstory/안드로이드-아키텍처-패턴-MVVM이-뭘까</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[LiveData]]></title>
            <link>https://velog.io/@hhi-5258/LiveData</link>
            <guid>https://velog.io/@hhi-5258/LiveData</guid>
            <pubDate>Thu, 11 Mar 2021 08:00:39 GMT</pubDate>
            <description><![CDATA[<h2 id="livedata">LiveData</h2>
<p><code>LiveData</code> 는 관찰 가능한(Observable) 데이터 클래스입니다. LiveData는 Lifecycle 을 통해 생명 주기를 인식합니다.
즉, 주어진 LifecycleOwner (액티비티, 프래그먼트, 서비스와 컴포넌트들) 의 생명 주기가 STARTED 또는 RESUME 상태인 경우만 Observer 를 활성(active) 상태로 봅니다.
LiveData의 장점들은 아래와 같습니다.</p>
<ul>
<li>UI와 data 의 동기화</li>
<li>memory leak 없음</li>
<li>액티비티가 갑작스럽게 종료될 때도 안전하다</li>
<li>생명 주기에 대한 고민 필요 없음</li>
<li>최신의 데이터를 유지한다</li>
<li>구성 변경에 대응한다</li>
<li>자원 공유하기</li>
</ul>
<h2 id="databinding-과-livedata-사용하기">DataBinding 과 LiveData 사용하기</h2>
<p>dataBinding 과 liveData를 사용하면 lifecycle 에 대한 걱정없이 데이터의 변경에 따른 UI 변경을 자동으로 처리하도록 설정할 수 있습니다. </p>
<ol>
<li>바인딩 클래스에 LifecycleOwner를 명시하여, 생명주기를 인식하도록 합니다.<pre><code class="language-kotlin">binding.lifecycleOwner = this</code></pre>
</li>
<li>ViewModel 에서 LiveData 생성할 때, ViewModel 에서만 값을 변경할 수 있도록 하는것이 보안에 좋습니다. 참조될 때는 LiveData, ViewModel 내에서는 MutableLiveData 로 변환하여 사용합니다.<pre><code class="language-kotlin">private val _hideKeyBoardEvent = MutableLiveData&lt;Unit&gt;()
val hideKeyBoardEvent: LiveData&lt;Unit&gt; = _hideKeyBoardEvent</code></pre>
</li>
<li>위와 같은 데이터를 Activity에서 observe 하도록 합니다. <pre><code class="language-kotlin">vm.hideKeyBoardEvent.observe(this, Observer {
...
}) </code></pre>
</li>
<li>xml 에서 dataBinding 합니다.<pre><code>android:onClick=&quot;@{()-&gt;vm.hideKeyBoardEvent()}&quot;</code></pre></li>
</ol>
<h2 id="setvalue-postvalue">setValue, postValue</h2>
<ul>
<li>setValue 는 mainThread 에서 값을 변경해줍니다. 그렇기 때문에, setValue 바로 뒤에 getValue를 해도 변경된 값을 받아옵니다. setValue 는 mainThread 에서 값을 dispatch 하기 때문에 백그라운드에서 setValue 를 호출하면 오류가 발생합니다.</li>
<li>postValue 는 백그라운드에서 값을 변경합니다. 백그라운드 thread에서 동작하다가 mainThread 로 값을 post 해주는 방식으로 동작합니다. 함수 내부적으로는 아래와 같은 코드가 동작합니다.
postValue 를 호출한 뒤 바로 getValue로 값을 읽으려고 하면, 변경되지 않은 값을 불러올 수도 있습니다. Handler 를 통해 mainThread 에 값이 적용되기 전에 getValue 로 호출하기 때문입니다.<pre><code class="language-kotlin">new Handler(Looper.mainLooper()).post(() -&gt; setValue())</code></pre>
</li>
</ul>
<hr>
<h2 id="reference">Reference</h2>
<p><a href="https://developer.android.com/topic/libraries/architecture/livedata">android developer</a>
<a href="https://medium.com/harrythegreat/jetpack-android-livedata-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0-ed49a6f17de3">해리의 유목코딩</a>
<a href="https://leveloper.tistory.com/179">꾸준하게</a></p>
]]></description>
        </item>
    </channel>
</rss>