<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>minnie_dev.log</title>
        <link>https://velog.io/</link>
        <description>Android Developer</description>
        <lastBuildDate>Fri, 16 Sep 2022 07:58:22 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>minnie_dev.log</title>
            <url>https://images.velog.io/images/minnie_dev/profile/1904345b-879e-4168-a7f5-352d22cecd65/KakaoTalk_20211216_151204155.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. minnie_dev.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/minnie_dev" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[Clone] 인스타그램 클론 프로젝트(7) - 좋아요 ]]></title>
            <link>https://velog.io/@minnie_dev/Clone-%EC%9D%B8%EC%8A%A4%ED%83%80%EA%B7%B8%EB%9E%A8-%ED%81%B4%EB%A1%A0-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B87-%EC%A2%8B%EC%95%84%EC%9A%94</link>
            <guid>https://velog.io/@minnie_dev/Clone-%EC%9D%B8%EC%8A%A4%ED%83%80%EA%B7%B8%EB%9E%A8-%ED%81%B4%EB%A1%A0-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B87-%EC%A2%8B%EC%95%84%EC%9A%94</guid>
            <pubDate>Fri, 16 Sep 2022 07:58:22 GMT</pubDate>
            <description><![CDATA[<h3 id="1-contentdto-데이터-타입-변경">1. ContentDTO 데이터 타입 변경</h3>
<p>ContentDTO에서 favorites는 중복 좋아요 방지할 수 있는 유저 확인 용 변수이다.
해당 데이터 타입을 Map에서 MutableMap으로 변경</p>
<pre><code class="language-kotlin">var favorites: MutableMap&lt;String, Boolean&gt; = HashMap()</code></pre>
<h3 id="2-좋아요-이벤트">2. 좋아요 이벤트</h3>
<p>firebase의 데이터 적재 스타일을 보면 <strong>collection - document - (collection|field)</strong> 와 같은 형식을 따른다.
images collection의 해당 uid를 가진 document 좋아요에 대한 정보를 넣어주기 위해 가져온다.</p>
<p>데이터를 저장하기 위해서 transaction을 사용한다.</p>
<p>좋아요 버튼을 누르면 favoriteCount를 1 증가시켜주고, favorites[uid]의 값을 true로 변경 시켜준다.</p>
<p>onBindViewHolder에서는 버튼을 눌렀을 때 favoriteEvent 함수를 호출하고 좋아요 상태에 따라 이미지를 적용시켜준다.</p>
<pre><code class="language-kotlin">private fun favoriteEvent(position: Int) {
    val tsDoc = firestore?.collection(&quot;images&quot;)
                ?.document(contentUIDList[position]) // images collection에서 원하는 uid의 document에 대한 정보
    // 데이터를 저장하기 위해 transaction 사용
    firestore?.runTransaction { transaction -&gt;
                uid = FirebaseAuth.getInstance().currentUser?.uid // uid 값 가져옴
                val contentDTO = transaction.get(tsDoc!!) // 해당 document 받아오기
                    .toObject(ContentDTO::class.java)//트랜젝션의 데이터를 ContentDTO로 캐스팅
                if (contentDTO!!.favorites.containsKey(uid)) { //좋아요 버튼이 이미 클릭 되어있으면 -&gt; favorites 값이 true이면
                    //When the button is clicked
                    contentDTO.favoriteCount = contentDTO.favoriteCount - 1
                    contentDTO.favorites.remove(uid);
                } else {
                    //When the button is not clicked
                    contentDTO.favoriteCount = contentDTO.favoriteCount + 1
                    contentDTO.favorites[uid!!] = true
                }
                transaction.set(tsDoc,contentDTO) // 해당 document에 Dto 객체 저장 , 트랜젝션을 다시 서버로 돌려줌
            }
        }

  override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { 
   //This code is when the button is clicked
   binding.detailviewitemFavoriteImageview.setOnClickListener {
                favoriteEvent(position)
   }
   //This code is when the page is loaded
   if(contentDTOs[position].favorites.containsKey(uid)){ // 좋아요 상태에 따라 이미지 적용
        //This is like status         
        binding.detailviewitemFavoriteImageview.setImageResource(R.drawable.ic_favorite)
   }else{
        //This is unlike status
        binding.detailviewitemFavoriteImageview.setImageResource(R.drawable.ic_favorite_border)
   }

}
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Android] 기존프로젝트에서 Jetpack Compose 사용]]></title>
            <link>https://velog.io/@minnie_dev/Android-%EA%B8%B0%EC%A1%B4%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8%EC%97%90%EC%84%9C-Jetpack-Compose-%EC%82%AC%EC%9A%A9</link>
            <guid>https://velog.io/@minnie_dev/Android-%EA%B8%B0%EC%A1%B4%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8%EC%97%90%EC%84%9C-Jetpack-Compose-%EC%82%AC%EC%9A%A9</guid>
            <pubDate>Thu, 01 Sep 2022 02:14:06 GMT</pubDate>
            <description><![CDATA[<h2 id="1-개발-환경-설정">1. 개발 환경 설정</h2>
<ul>
<li><p>Jetpack Compose로 최적의 환경에서 개발하려면 Android 스튜디오 버전에 맞는 Android Gradle 플러그인을 구성해야 합니다.
build.gradle(project) 에서 해당 버전에 맞게 구성합니다.</p>
<pre><code>buildscript {
  ...
  dependencies {
      classpath &quot;com.android.tools.build:gradle:7.2.2&quot;
      ...
  }
}</code></pre></li>
<li><p>kotlin 구성
build.gradle(app)에서 프로젝트에서 코틀린을 사용하는지 확인합니다.</p>
<pre><code>plugins {
  id &#39;kotlin-android&#39;
}</code></pre></li>
<li><p>Gradle 구성
앱의 최소 API 수준을 21 이상으로 설정하고 build.gradle(app)에서 Jetpack Compose를 사용 설정해야합니다. kotlin 컴파일러 플러그인의 버전도 설정해야합니다.</p>
<pre><code>  buildFeatures {
      // Enables Jetpack Compose for this module
      compose true
  }

  composeOptions {
      kotlinCompilerExtensionVersion &#39;1.3.0&#39;
  }</code></pre></li>
</ul>
<h2 id="2-jetpack-compose-도구-키트-종속-항목-추가">2. Jetpack Compose 도구 키트 종속 항목 추가</h2>
<p>build.gradle 파일에 Jetpack Compose 도구 키트 종속 항목을 포함해야합니다.</p>
<pre><code>dependencies {
    // Integration with activities
    implementation &#39;androidx.activity:activity-compose:1.5.1&#39;
    // Compose Material Design
    implementation &#39;androidx.compose.material:material:1.2.1&#39;
    // Animations
    implementation &#39;androidx.compose.animation:animation:1.2.1&#39;
    // Tooling support (Previews, etc.)
    implementation &#39;androidx.compose.ui:ui-tooling:1.2.1&#39;
    // Integration with ViewModels
    implementation &#39;androidx.lifecycle:lifecycle-viewmodel-compose:2.5.1&#39;
    // UI Tests
    androidTestImplementation &#39;androidx.compose.ui:ui-test-junit4:1.2.1&#39;
}</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[영어 공부] Grammar GateWay Basic - Unit 021~030📗]]></title>
            <link>https://velog.io/@minnie_dev/%EC%98%81%EC%96%B4-%EA%B3%B5%EB%B6%80-Grammar-GateWay-Basic-Unit-021022</link>
            <guid>https://velog.io/@minnie_dev/%EC%98%81%EC%96%B4-%EA%B3%B5%EB%B6%80-Grammar-GateWay-Basic-Unit-021022</guid>
            <pubDate>Fri, 26 Aug 2022 13:30:33 GMT</pubDate>
            <description><![CDATA[<h3 id="unit-021-과거-시제와-현재완료-시제-비교">Unit 021 과거 시제와 현재완료 시제 비교</h3>
<blockquote>
<p>지금과 관련짓지 않고 과거에 끝난 일이나 상황 자체에 대해서만 말 할 때는 과거 시제를 쓴다.
과거에 일어난 일이나 상황이 지금과 관련되어 있음을 말할 때는 현재완료 시제를 쓴다.
지금까지 어떤 일이 계속되고 있다는 의미로 말할 때는 현재완료 시제를 쓴다. 이때, 과거 시제를 쓰지 않는 것에 주의한다.
이미 지나간 과거의 시점에 대해 말 할 때는 과거 시제만 쓰는 것에 주의한다.</p>
</blockquote>
<h3 id="unit-022-just-already-yet">Unit 022 just, already, yet</h3>
<blockquote>
<p>have/has + just + 과거분사 : 방금 막~ 했다.
have/has + already + 과거분사 : (예상보다 앞서)이미 <del>했다.
have/has + not + 과거분사 ~ yet (부정문) : 아직 ~하지 않았다.
have/has</del> + 과거분사 ~ yet (의문문) : 아직 ~하지 않았나요?
just, already, yet은 현재완료 시제와 함꼐 쓸 수도 있고, 과거 시제와 함께 쓸 수도 있다. 이때 의미 차이가 없다.</p>
</blockquote>
<h3 id="unit-023-미래-시제-1-will">Unit 023 미래 시제 (1) will</h3>
<blockquote>
<p>미래 시제는 &#39;~할 것 이다&#39;라는 의미로 will+동사원형으로 쓴다
미래의 사실이나 미래에 일어날 것이라고 생각하는 일에 대해 말할 따 쓴다.
미래에 어떤 일을 할 것이라고 말하고 있는 시점인 지금 결정하는 경우에도 쓴다.
will 부정문 Will not won&#39;t
Will 의문문 Will you ..?</p>
</blockquote>
<h3 id="unit-024-미래시제-2-be-going-to">Unit 024 미래시제 (2) be going to</h3>
<blockquote>
<p>~할 것이다라는 의미로 말할 때 be going to 동시원형
이미 하기로 결정한 미래의 일에 대해 말할 때
지금 상황을 근거로 미래에 어떤 일이 확실히 일어날 것이라고 말할 때도 사용
부정문이나 의문문은 be동사 부정문과 의문문 처럼 사용하면된다</p>
</blockquote>
<h3 id="unit-025-미래-시제-3-미래를-나타내는-현재진행과-현재-시제">Unit 025 미래 시제 (3) 미래를 나타내는 현재진행과 현재 시제</h3>
<blockquote>
<p>구체적으로 계획을 세워 군 미래의 일(약속, 예약) 에 대해 말할 때 be동사 +-ing 현재가 아닌 미래
대중교통,수업 등 이미 짜여 있는 시간표, 일정표상의 일에 대해 말할 깨는 미래의 일이라도 현재 시제를 쓸 수 있다</p>
</blockquote>
<h3 id="unit-026-can-could">Unit 026 can, could</h3>
<blockquote>
<p>can 동사원형 ~ 할 수 있다
부정문 can not
의문문 Can 주어 동사
과거형 could</p>
</blockquote>
<h3 id="unit-027-might-may">Unit 027 might, may</h3>
<blockquote>
<p>might + 동사 원형 : ~할지도 모른다.
might와 동일한 의미로 may도 쓸 수 있다.
일상생활에서는 might를 더 많이 쓴다.</p>
</blockquote>
<h3 id="unit-028-cancould-i---cancould-yot---may-i-">Unit 028 Can/Could I ~? , Can/Could yot ~? / May I ~?</h3>
<blockquote>
<p>Can I <del>? 해도 될까요?
Can I와 같은 의미이지만 더 공손하게 말할 때 Could I ~? 또는 May I ? 를 쓸수 있다.
Can You ~? 해주시겠어요?
Can you 와 같은 의미이지만 더 공손하게 말할 때 Could you를 쓸 수 있다.
Can/Could you</del>? 대신 May you~? 는 쓸 수 없는 것에 주의한다.</p>
</blockquote>
<h3 id="unit-029-must">Unit 029 must</h3>
<blockquote>
<p>must 동사원형 : 반드시 해야한다
must not 동사원형 : 반드시 해서는 안된다
과거형으로 쓸때는 had to</p>
</blockquote>
<h3 id="unit-030-have-to">Unit 030 have to</h3>
<blockquote>
<p>have/has to + 동사원형 : (반드시) ~해야 한다.
have/has to와 같은 의미로 must를 쓸 수 있다. 단, 일상 대화에서는 have/has to를 주로쓴다.
don&#39;t/ doesn&#39;t have to 동사원형 : ~할 필요가 없다.
must not은 해서는 안된다, have to의 부정문은 할 필요가없다.
have/has to의 과거는 had to로 쓴다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[영어 공부] Grammar GateWay Basic - unit 011~020📗]]></title>
            <link>https://velog.io/@minnie_dev/%EC%98%81%EC%96%B4-%EA%B3%B5%EB%B6%80-Grammar-GateWay-Basic-unit-011012</link>
            <guid>https://velog.io/@minnie_dev/%EC%98%81%EC%96%B4-%EA%B3%B5%EB%B6%80-Grammar-GateWay-Basic-unit-011012</guid>
            <pubDate>Tue, 16 Aug 2022 11:10:22 GMT</pubDate>
            <description><![CDATA[<h3 id="unit-011-be동사-과거-시제-waswere">Unit 011 be동사 과거 시제 (was/were)</h3>
<blockquote>
<p>was/were는 &#39;<del>이었다.&#39; &#39;</del>에 있었다&#39;
am/is의 과거는 was로 are의 과거는 were로 쓰인다</p>
</blockquote>
<h3 id="unit-012-be동사-과거-시제-부정문과-의문문">Unit 012 be동사 과거 시제 부정문과 의문문</h3>
<blockquote>
<p>was/were의 부정문 : was not, were not
was/were의 의문문
Was I/he/she/it ...? -&gt;  대답 : Yes, I/He/She/It was 
Were we/you/they -&gt; 대답 Yes, You/They/We were</p>
</blockquote>
<h3 id="unit-013-일반동사-과거-시제">Unit 013 일반동사 과거 시제</h3>
<blockquote>
<p>&#39;~했다&#39;라는 의미로 과거에 일어난 일에 대해 말할 때 과거 시제를 쓴다.
과거 시제는 주로 동사원형 끝에 -ed를 붙인다.</p>
</blockquote>
<h3 id="unit-014-과거-시제-부정문과-의문문">Unit 014 과거 시제 부정문과 의문문</h3>
<blockquote>
<p>과거 시제 부정문은 did not + 동사 원형으로 쓴다.
의문문은 did + 주어 + 동사원형으로 쓴다.
답할 때는 yes I/he/she/it/we/you/they did
Oh, We didn&#39;t lock the front door.
Let&#39;s go back home right away
find 과거형 found등 주의해야 할 과거형이 있다.</p>
</blockquote>
<h3 id="unit-015-과거진행-시제">Unit 015 과거진행 시제</h3>
<blockquote>
<p>과거진행 시제는 &#39;~하는 중이었다&#39;라는 의미로 was/were + -ing를 사용한다.
am/is의 과거형으로 was를 쓰고, are의 과거형으로 were을 쓴다.
지금 말하고 있는 시점에 일어나고 있는 일에 대해 말할 때 현재진행 시제를 쓴다.
과거의 특정한 시점에 일어나고 있던 일에 대해 말할 때 과거진행 시제를 쓴다.
was waving : ~흔드는 중이었다.
was lying in bed : 침대에 누웠는 중이었다.</p>
</blockquote>
<h3 id="unit-016-과거진행-시제-부정문과-의문문">Unit 016 과거진행 시제 부정문과 의문문</h3>
<blockquote>
<p>과거진행 시제 부정문은 다음과 같이 쓴다.
I/he/she/it was not + -ing
They/you/we were not + -ing
과거진행 시제 의문문은 다음과 같이 쓴다.
Was I/he -ing?
Were You/they -ing?</p>
</blockquote>
<h3 id="unit-017-used-to">Unit 017 used to</h3>
<blockquote>
<p>used to + 동사원형 : (과거에는) ~했었다. (지금은 아니다)
used to의 부정문은 didn&#39;t use to, 의문문은 did + 주어 + use to ~?
used to는 지금 더 이상 계속되지 않는 과거의 습관이나 상태에 대해 말할 때 쓴다.
현재에 대해 말할 때는 used to를 쓰지 않는 것에 주의한다.
crowd : 사람들, 군중, 가득 메우다</p>
</blockquote>
<h3 id="unit-018-현재-완료-시제1">Unit 018 현재 완료 시제(1)</h3>
<blockquote>
<p>현재완료 시제는 have/has + 과거분사
현재완료 시제 부정문 : have/has not +과거분사
현재완료 시제 의문문 : Have/Has + 주어 + 과거분사?
현재완료 시제는 과거부터 현재까지를 포괄하는 시제로, 과거에 있었던 일을 현재와 관련지어 말할 때 쓴다.</p>
</blockquote>
<h3 id="unit-019-현재완료-시제-2-현재까지-계속되는-일">Unit 019 현재완료 시제 (2) 현재까지 계속되는 일</h3>
<blockquote>
<p>현재완료 시제는 &#39;(과거부터 지금까지) ~해왔다&#39;라는 의미로 말할 때 쓸 수 있다.
이때 for, since와 자주 함께 쓴다
have/has + 과거분사 + for + 기간 : ~동안 ... 해왔다.
have/has + 과거분사 + since + 기간 : ~부터 ... 해왔다.
How long have/has ~? : ~한 지 얼마나 되셨나요?
be의 과거분사 : been</p>
</blockquote>
<h3 id="unit-020-현재완료-시제-3-지금까지-경험해본-일">Unit 020 현재완료 시제 (3) 지금까지 경험해본 일</h3>
<blockquote>
<p>현재완료 시제는 &#39;(과거부터 지금까지) <del>해본 적이 있다&#39;라는 의미로 말할 때 쓸 수 있다.
Have you ever + 과거분사 ~? : (지금까지) ~해본 적이 있나요?
has/have never + 과거분사 : ~해본 적이 전혀 없다.
have/has been (to</del>) : ~ 에 가본 적이 있다.
have gone으로 안쓰게 유의 : ~에 가고없다.
swim의 과거분사 : swum</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[영어 공부] Grammar GateWay Basic - unit 001~0010📗]]></title>
            <link>https://velog.io/@minnie_dev/%EC%98%81%EC%96%B4-%EA%B3%B5%EB%B6%80-Grammar-GateWay-Basic-unit-001002</link>
            <guid>https://velog.io/@minnie_dev/%EC%98%81%EC%96%B4-%EA%B3%B5%EB%B6%80-Grammar-GateWay-Basic-unit-001002</guid>
            <pubDate>Mon, 01 Aug 2022 12:07:18 GMT</pubDate>
            <description><![CDATA[<h3 id="unit-001-he-is-a-student----be동사">Unit 001 He is a student  - be동사</h3>
<blockquote>
<p>am/is/are 는 ~이다, ~에 있다</p>
</blockquote>
<h3 id="unit-002-he-is-not--hungry">Unit 002 He is not  hungry</h3>
<blockquote>
<p>am/is/are + not : ~ 이 아니다. ~에 없다.</p>
</blockquote>
<p>My office isn’t a far from home. It’s only three blocks away.
세 블록 떨어져있다. (Blocks away : 떨어져있다)</p>
<h3 id="unit-003-be동사-의문문">Unit 003 be동사 의문문</h3>
<blockquote>
<ul>
<li>be동사 의문문에서는 am/are/is를 주어 앞에 쓴다.
ex) Am I ? Are we? Is it?</li>
</ul>
</blockquote>
<ul>
<li>의문문에서는 짧게 답할 수 있다. Yes 뒤에는 축약형을 사용하지 않는 것에 주의한다.
ex) Yes, I am, No, I am not</li>
</ul>
<h3 id="unit-004-현재진행-시제">Unit 004 현재진행 시제</h3>
<blockquote>
<ul>
<li>현재진행 시제 (지금) ~하는 중이다.
am/is/are + -ing</li>
</ul>
</blockquote>
<ul>
<li>ing 를 붙일 때 주의해야 할 동사들이 있다
기본적으로는 +ing
e일 때는 e를 빼고 +-ing
ie 일때는 y로 바꾸고 + ing
단모음+단자음으로 끝날때는 자음 한번 더 쓰고 + -ing</li>
</ul>
<h3 id="unit-005-현재진행-시제-부정문과-의문문">Unit 005 현재진행 시제 부정문과 의문문</h3>
<blockquote>
<ul>
<li>현재진행 시제 부정문은 am/are/is + not + -ing로 쓴다</li>
</ul>
</blockquote>
<ul>
<li>현재진행 시제 의문문은 am/are/is + 주어 + -ing</li>
</ul>
<h3 id="unit-006-일반동사-현재-시제1">Unit 006 일반동사 현재 시제(1)</h3>
<blockquote>
<p>He/she/it 는 동사 뒤에 s/es 를 붙인다.</p>
</blockquote>
<h3 id="unit-007-일반동사-현재-시제2">Unit 007 일반동사 현재 시제(2)</h3>
<blockquote>
<p>반복적으로 일어나는 일에 대해 말하거나, 일반적인 사실, 변하지 않는 사실 또는 과학적인 사실을 말할 때 현재 시제를 사용한다</p>
</blockquote>
<h3 id="unit-008-현재-시제-부정문">Unit 008 현재 시제 부정문</h3>
<blockquote>
<p>현재 시제 부정문은 do/does not + 동사원형으로 쓴다</p>
</blockquote>
<h3 id="unit-009-현재-시제-의문문">Unit 009 현재 시제 의문문</h3>
<blockquote>
<p>현재 시제 의문문은 do/does + 주어 + 동사원형으로 쓰인다</p>
</blockquote>
<h3 id="unit-010-현재진행-시제와-현재-시제-비교">Unit 010 현재진행 시제와 현재 시제 비교</h3>
<blockquote>
<ul>
<li>현재진행 시제는 지금 말하고 있는 시점에서 일어나고 있는 일을 나타낸다
다음과 같은 동사들은 현재진행 시제로 쓰지 않고 주로 현재 시제로 쓴다
ex) want, love, prefer, know, agree, remember, need, like, hate, believe, understand, forget</li>
</ul>
</blockquote>
<ul>
<li>have는 ~가지고 있다라는 의미로 말할 때는 현재진행 시제로 쓰지않고 현재 시제로 쓴다.
그러나 ~먹다라는 의미로 말할 때는 현재진행 시제와 현재 시제 둘다 쓸수있다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Clone] 인스타그램 클론 프로젝트(6) - 상세화면 페이지]]></title>
            <link>https://velog.io/@minnie_dev/Clone-%EC%9D%B8%EC%8A%A4%ED%83%80%EA%B7%B8%EB%9E%A8-%ED%81%B4%EB%A1%A0-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B86</link>
            <guid>https://velog.io/@minnie_dev/Clone-%EC%9D%B8%EC%8A%A4%ED%83%80%EA%B7%B8%EB%9E%A8-%ED%81%B4%EB%A1%A0-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B86</guid>
            <pubDate>Tue, 29 Mar 2022 02:55:19 GMT</pubDate>
            <description><![CDATA[<p><strong>7강 하울스타그램 상세화면 페이지 만들기</strong></p>
<h3 id="1-glide-라이브러리-추가">1. Glide 라이브러리 추가</h3>
<p><strong>build.gradle(:app)</strong></p>
<pre><code>//glide
    implementation &#39;com.github.bumptech.glide:glide:4.11.0&#39;</code></pre><h3 id="2-recyclerview-item-생성">2. RecyclerView Item 생성</h3>
<p>사용자의 id, profile 이미지, 업로드 한 이미지, 좋아요 버튼, 댓글 버튼, 좋아요 갯수, 글 내용에 대한 정보들을 담는 item을 생성해준다.</p>
<pre><code>&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;LinearLayout xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    android:layout_width=&quot;match_parent&quot;
    android:layout_height=&quot;match_parent&quot;
    android:orientation=&quot;vertical&quot;&gt;

    &lt;LinearLayout
        android:layout_width=&quot;match_parent&quot;
        android:layout_height=&quot;50dp&quot;
        android:gravity=&quot;center_vertical&quot;&gt;

        &lt;ImageView
            android:id=&quot;@+id/detailviewitem_profile_image&quot;
            android:layout_width=&quot;35dp&quot;
            android:layout_height=&quot;35dp&quot;
            android:layout_margin=&quot;7.5dp&quot;
            android:src=&quot;@mipmap/ic_launcher&quot; /&gt;

        &lt;TextView
            android:id=&quot;@+id/detailviewitem_profile_textview&quot;
            android:layout_width=&quot;wrap_content&quot;
            android:layout_height=&quot;wrap_content&quot;
            android:text=&quot;User name&quot; /&gt;
    &lt;/LinearLayout&gt;

    &lt;ImageView
        android:id=&quot;@+id/detailviewitem_imageview_content&quot;
        android:layout_width=&quot;match_parent&quot;
        android:layout_height=&quot;250dp&quot;
        android:scaleType=&quot;fitXY&quot; /&gt;

    &lt;LinearLayout
        android:layout_width=&quot;match_parent&quot;
        android:layout_height=&quot;50dp&quot;
        android:gravity=&quot;center_vertical&quot;&gt;

        &lt;ImageView
            android:id=&quot;@+id/detailviewitem_favorite_imageview&quot;
            android:layout_width=&quot;35dp&quot;
            android:layout_height=&quot;35dp&quot;
            android:layout_marginStart=&quot;8dp&quot;
            android:src=&quot;@drawable/ic_favorite_border&quot; /&gt;

        &lt;ImageView
            android:id=&quot;@+id/detailviewitem_comment_imageview&quot;
            android:layout_width=&quot;35dp&quot;
            android:layout_height=&quot;35dp&quot;
            android:src=&quot;@drawable/ic_chat_black&quot; /&gt;
    &lt;/LinearLayout&gt;

    &lt;TextView
        android:id=&quot;@+id/detailviewitem_favoritecounter_textview&quot;
        android:layout_width=&quot;wrap_content&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:text=&quot;like 0&quot; /&gt;

    &lt;TextView
        android:id=&quot;@+id/detailviewitem_explain_textview&quot;
        android:layout_width=&quot;wrap_content&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:layout_marginHorizontal=&quot;8dp&quot;
        android:text=&quot;Explain content&quot; /&gt;

&lt;/LinearLayout&gt;</code></pre><h3 id="3-recyclerview-adapter-생성">3. Recyclerview Adapter 생성</h3>
<p>RecyclerView Adapter를 생성하고 그 안에 사용자 정보 List인 contentUIDList와 업로드 내용들의 정보가 담긴 contentDTOs를 만들어준다.</p>
<p>처음에 만들어 질 때 아래와 같이 firestore에 올라간 정보들을 얻어서 리스트에 넣어준다.</p>
<pre><code class="language-kotlin">init {
    firestore?.collection(&quot;images&quot;)?.orderBy(&quot;timestamp&quot;)
                ?.addSnapshotListener { querySnapshot, firebaseFirestorException -&gt;
                    contentDTOs.clear()
                    contentUIDList.clear()
                    for (snapshot in querySnapshot!!.documents) {
                        var item = snapshot.toObject(ContentDTO::class.java)
                        contentDTOs.add(item!!)
                        contentUIDList.add(snapshot.id)
                    }
                    notifyDataSetChanged()
                }
    }</code></pre>
<p>이미지의 경우에는 Glide를 사용하여 로드해준다.</p>
<pre><code class="language-kotlin">Glide.with(holder.itemView.context).load(contentDTOs[position].imageUrl)
                .into(binding.detailviewitemImageviewContent)</code></pre>
<p>DetailViewRecylcerViewAdapter에 대한 코드이다.</p>
<pre><code class="language-kotlin">inner class DetailViewRecyclerViewAdapter : RecyclerView.Adapter&lt;RecyclerView.ViewHolder&gt;() {
        var contentDTOs: ArrayList&lt;ContentDTO&gt; = arrayListOf()
        var contentUIDList: ArrayList&lt;String&gt; = arrayListOf()
        lateinit var binding: ItemDetailBinding

        init {
            firestore?.collection(&quot;images&quot;)?.orderBy(&quot;timestamp&quot;)
                ?.addSnapshotListener { querySnapshot, firebaseFirestorException -&gt;
                    contentDTOs.clear()
                    contentUIDList.clear()
                    for (snapshot in querySnapshot!!.documents) {
                        var item = snapshot.toObject(ContentDTO::class.java)
                        contentDTOs.add(item!!)
                        contentUIDList.add(snapshot.id)
                    }
                    notifyDataSetChanged()
                }
        }

        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
            binding = ItemDetailBinding.inflate(LayoutInflater.from(parent.context), parent, false)
            return CustomViewHolder(binding)
        }

        inner class CustomViewHolder(binding: ItemDetailBinding) :
            RecyclerView.ViewHolder(binding.root) {

        }

        override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
            //user id
            binding.detailviewitemProfileTextview.text = contentDTOs[position].userId

            //image
            Glide.with(holder.itemView.context).load(contentDTOs[position].imageUrl)
                .into(binding.detailviewitemImageviewContent)

            //explain of content
            binding.detailviewitemExplainTextview.text = contentDTOs[position].explain

            //likes
            binding.detailviewitemFavoritecounterTextview.text =
                &quot;Likes  ${contentDTOs[position].favoriteCount}&quot;

            //profile
            Glide.with(holder.itemView.context).load(contentDTOs[position].imageUrl)
                .into(binding.detailviewitemProfileImage)

        }

        override fun getItemCount(): Int {
            return contentDTOs.size
        }
    }</code></pre>
<h3 id="4-recyclerview-setadpater">4. RecyclerView setAdpater</h3>
<p>DetailViewFragment에서 Recyclerview에 위에서 생성한 Adapter를 set해주고
layoutManager를 지정해준다.</p>
<pre><code class="language-kotlin">binding.detailviewfragmentRecyclerview.adapter = DetailViewRecyclerViewAdapter()
binding.detailviewfragmentRecyclerview.layoutManager = LinearLayoutManager(activity)</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[안드로이드 스튜디오 프로젝트/패키지 명 변경하기]]></title>
            <link>https://velog.io/@minnie_dev/%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-%EC%8A%A4%ED%8A%9C%EB%94%94%EC%98%A4-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8%ED%8C%A8%ED%82%A4%EC%A7%80-%EB%AA%85-%EB%B3%80%EA%B2%BD%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@minnie_dev/%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-%EC%8A%A4%ED%8A%9C%EB%94%94%EC%98%A4-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8%ED%8C%A8%ED%82%A4%EC%A7%80-%EB%AA%85-%EB%B3%80%EA%B2%BD%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 16 Mar 2022 01:09:30 GMT</pubDate>
            <description><![CDATA[<h3 id="1-디렉토리명-변경">1. 디렉토리명 변경</h3>
<p>Compat Middle Package 메뉴의 설정을 해제 시켜줍니다. 패키지명을 기준으로 폴더가 구조화 되어있는 것을 해제시켜주는 작업입니다.</p>
<p><img src="https://images.velog.io/images/minnie_dev/post/16349520-211a-4bda-b128-a8a2afe1be54/image.png" alt=""></p>
<p>이렇게 디렉토리가 해제되면 폴더 이름을 변경할 수 있습니다.
Refactory를 통해 이름을 수정하고 Rename 선택 후 하단의 DO Refactor를 선택하여 변경시켜주면 됩니다.</p>
<h3 id="2-buildgradle-파일-변경">2. build.gradle 파일 변경</h3>
<p>build.gradle에서 applicationId를 변경시켜 줍니다.
<img src="https://images.velog.io/images/minnie_dev/post/364b5ca3-9a1c-4029-8dbd-cb7336d45d91/image.png" alt=""></p>
<h3 id="3-rebuild-project-후-프로젝트-폴더명-변경">3. Rebuild Project 후 프로젝트 폴더명 변경</h3>
<p>프로젝트를 Rebuild 하고 프로젝트 폴더명을 변경시켜주면 됩니다.</p>
<p>참조 : <a href="https://android-dev.tistory.com/20">https://android-dev.tistory.com/20</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Clone] 인스타그램 클론 프로젝트(5) - 컨텐츠 데이터 모델]]></title>
            <link>https://velog.io/@minnie_dev/%EC%9D%B8%EC%8A%A4%ED%83%80%EA%B7%B8%EB%9E%A8-%ED%81%B4%EB%A1%A0%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8</link>
            <guid>https://velog.io/@minnie_dev/%EC%9D%B8%EC%8A%A4%ED%83%80%EA%B7%B8%EB%9E%A8-%ED%81%B4%EB%A1%A0%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8</guid>
            <pubDate>Tue, 15 Mar 2022 08:15:41 GMT</pubDate>
            <description><![CDATA[<p>인프런 하울스타그램 컨텐츠 데이터 모델 강의로 업로드한 사진을 데이터로 관리할 수 있도록 데이터 모델 만드는 내용이다.</p>
<h3 id="1-의존성-추가">1. 의존성 추가</h3>
<p>먼저 firebase의 fireStore 의존성을 추가해준다.</p>
<pre><code>implementation &#39;com.google.firebase:firebase-firestore-ktx:24.0.2&#39;</code></pre><h3 id="2-데이터-클래스-생성">2. 데이터 클래스 생성</h3>
<p>ContentDTO 데이터 클래스를 생성한다.</p>
<pre><code class="language-kotlin">
data class ContentDTO(
    var explain: String? = null, // 컨텐츠 설명 관리
    var imageUrl: String? = null, // 이미지 주소 관리
    var uid: String? = null, // 어느 유저가 올렸는지 관리
    var userId: String? = null, // 올린 유저의 이미지를 관리
    var timestamp: Long? = null, // 몇시 몇분에 컨텐츠를 올렸는지
    var favoriteCount: Int = 0, // 좋아요를 몇 개 눌렀는지
    var favorites: Map&lt;String, Boolean&gt; = HashMap() // 중복 좋아요 방지할 수 있는 유저 확인
) {
    data class Comment( // 댓글 관리
        var uid: String? = null, // uid 관리
        var userId: String? = null, // 이메일 관리
        var comment: String? = null, // 댓글 관리
        var timestamp: Long? = null // 시간
    )
}</code></pre>
<h3 id="3-addphotoactivity에서-데이터베이스-사용">3. AddphotoActivity에서 데이터베이스 사용</h3>
<p>AddPhotoActivity.kt에서 유저 정보를 가져올 수 있도록 auth변수를 선언하고 데이터베이스를 사용할 수 있도록 firebaseStore변수를 선언한다.</p>
<pre><code class="language-kotlin">var auth : FirebaseAuth? = null
var firebaseStore : FirebaseFirestore? = null</code></pre>
<p>onCreate내부에서 auth와 firebaseStore를 초기화 해준다.</p>
<pre><code class="language-kotlin">auth = FirebaseAuth.getInstance()
firebaseStore = FirebaseFirestore.getInstance()</code></pre>
<p>contentUpload 메서드 안에서 데이터베이스를 입력해주는 코드를 작성해준다.
방식에는 Callback 방식과 Promise 방식 2가지 방식이 있지만 구글에서는 Promise방식을 권장한다고 한다.</p>
<pre><code class="language-kotlin">    @SuppressLint(&quot;SimpleDateFormat&quot;)
    private fun contentUpload(){
        //파일 이름 생성
        val timestamp = SimpleDateFormat(&quot;yyyyMMdd_HHmmss&quot;).format(Date())
        val imageFileName = &quot;IMAGE_&quot;+ timestamp + &quot;_.png&quot;

        val storageRef = storage?.reference?.child(&quot;images&quot;)?.child(imageFileName)

        //파일 업로드 1. 콜백방식
/*        storageRef?.putFile(photoUri!!)?.addOnSuccessListener {
            storageRef.downloadUrl.addOnSuccessListener { uri-&gt;
                var contentDTO = ContentDTO()
                contentDTO.imageUrl = uri.toString()

                contentDTO.uid = auth?.currentUser?.uid
                contentDTO.userId = auth?.currentUser?.email
                contentDTO.explain = binding.addphotoEditExplain.text.toString()
                contentDTO.timestamp= System.currentTimeMillis()

                firebaseStore?.collection(&quot;images&quot;)?.document()?.set(contentDTO)
                setResult(Activity.RESULT_OK)
                finish()
            }
            //Toast.makeText(this,getString(R.string.upload_success),Toast.LENGTH_LONG).shw()
        }*/

        //파일 업로드 2. 프라미스 방식
        storageRef?.putFile(photoUri!!)?.continueWithTask { task: Task&lt;UploadTask.TaskSnapshot&gt;-&gt;
            return@continueWithTask storageRef.downloadUrl
        }?.addOnSuccessListener { uri-&gt;
            var contentDTO = ContentDTO()
            contentDTO.imageUrl = uri.toString()

            contentDTO.uid = auth?.currentUser?.uid
            contentDTO.userId = auth?.currentUser?.email
            contentDTO.explain = binding.addphotoEditExplain.text.toString()
            contentDTO.timestamp= System.currentTimeMillis()

            firebaseStore?.collection(&quot;images&quot;)?.document()?.set(contentDTO)
            setResult(Activity.RESULT_OK)
            finish()
        }

    }</code></pre>
<p> 이미지 업로드가 완료되면 이미지 주소를 받아오는 코드를 addOnSuccessListener에 작성해준다. 이미지 주소를 받아오자마자 데이터 모델 ContentDTO를 만들어 주고 데이터 값을 넣어준다.
 setResult는 업로드가 완료되면 finish로 창을 닫아주고 정상적으로 창이 닫혔다는 플래그 값을 넘겨주기 위해 RESULT_OK를 사용해준다.</p>
<h3 id="4-firebase에서-데이터-베이스를-생성하고-설정">4. Firebase에서 데이터 베이스를 생성하고 설정</h3>
<p> <img src="https://images.velog.io/images/minnie_dev/post/bd1f66c1-aabd-4e9b-bf9e-cc017f7d8173/image.png" alt=""></p>
<p><img src="https://images.velog.io/images/minnie_dev/post/8aacacd7-8ab5-4609-8bfe-57f8d72cbd7b/image.png" alt=""></p>
<p><img src="https://images.velog.io/images/minnie_dev/post/ad3a7a78-843a-4421-b516-8ea09f2e4f06/image.png" alt=""></p>
<p>allow read, write: if request.time &lt; timestamp.date(2022, 4, 14); 이러한 규칙을</p>
<p><strong>allow read, write: if request.auth.uid !=null;</strong> 다음과 같이 수정해준다.</p>
<p><img src="https://images.velog.io/images/minnie_dev/post/63b4b37c-2041-4029-b78e-6bee5b098a87/image.png" alt=""></p>
<p><img src="https://images.velog.io/images/minnie_dev/post/dccb391d-e5a9-4be0-9a3d-0c3677df3ee2/image.png" alt=""></p>
<p>이후 이미지를 업로드 하고 Firebase의 데이터베이스에서 확인해보면 이미지가 저장된것을 확인할 수 있다.</p>
<p><img src="https://images.velog.io/images/minnie_dev/post/b8775633-69db-4a26-9cb3-f0ecae6d3d8f/image.png" alt=""></p>
<p>출처 : <a href="https://hyeals.tistory.com/41">https://hyeals.tistory.com/41</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[RxJava] RxJava 스케쥴러]]></title>
            <link>https://velog.io/@minnie_dev/RxJava-Observable7</link>
            <guid>https://velog.io/@minnie_dev/RxJava-Observable7</guid>
            <pubDate>Fri, 11 Mar 2022 08:51:32 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>📝RxJava의 학습 순서</strong></p>
</blockquote>
<ol>
<li>Observable 클래스를 명확하게 이해(특히 Hot Observable과 Cold Observable의 개념을 꼭 이해해야함)</li>
<li>변환, 제어, 결합 연산자 등 카테고리별 주요 함수 공부
<span style ="color:blue"><strong>3. 스케줄러의 의미, subscribeOn()과 observeOn()함수의 차이</strong></span></li>
<li>그 밖의 디버깅, 흐름 제어함수</li>
</ol>
<hr>
<h2 id="1rxjava-scheduler">1.RxJava Scheduler</h2>
<p>RxJava에서의 Scheduler는 RxJava 비동기 프로그래밍을 위한 쓰레드 관리자이며
즉, 스케쥴러를 이용해서 어떤 쓰레드에서 무엇을 처리할 지에 대해서 제어할 수 있다.
RxJava만 사용한다고 해서 비동기 처리가되는 것이 아닌 Scheduler를 통해 쓰레드를 분리해주어야 비동기 작업이 가능한 것이다.</p>
<p>Scheduler를 이용해서 데이터를 통지하는 쪽과 데이터를 처리하는 쪽 쓰레드를 별도로 지정해서 분리할 수 있고 쓰레드를 위한 코드의 간결성과 쓰레드의 복잡함을 줄일 수 있다.
스케줄러를 지정하기 위해서 생산자쪽의 데이터 흐름을 제어하기 위해서는 subscribeOn(), 구독자쪽에서 전달받은 데이터 처리를 제어하기 위해서는 observeOn() 연산자를 사용한다.</p>
<p><strong>subscribeOn은 여러번 호출되더라도 맨 처음의 호출만 영향을 주며 어디에 위치하든 상관 없고, observeOn은 여러번 호출될 수 있으며 이후에는 실행되는 연산에 영향을 주므로 위치가 중요하다.</strong></p>
<pre><code class="language-java">
Observable.fromIterable(shapes) // shapes 리스트는 (Red,Ball), (Green, Ball), (Blue,Ball) 이렇게 구성되어 있습니다.
        .subscribeOn(Schedulers.computation())
        .subscribeOn(Schedulers.io())
        .doOnSubscribe(data -&gt; MyUtil.printData(&quot;doOnSubscribe&quot;)) // printData System.out.println(&quot;&quot;+Thread.currentThread().getName()+&quot; | &quot;+message+&quot; | &quot;)
        .doOnNext(data -&gt; MyUtil.printData(&quot;doOnNext&quot;, data))
        .observeOn(Schedulers.newThread())                  
        .map(data -&gt; {data.shape = &quot;Square&quot;; return data;})
        .doOnNext(data -&gt; MyUtil.printData(&quot;map(Square)&quot;, data))
        .observeOn(Schedulers.newThread())                  
        .map(data -&gt; {data.shape = &quot;Triangle&quot;; return data;})
        .doOnNext(data -&gt; MyUtil.printData(&quot;map(Triangle)&quot;, data))
        .observeOn(Schedulers.newThread())                  
        .subscribe(data -&gt; MyUtil.printData(&quot;subscribe&quot;, data));</code></pre>
<p>subscribeOn의 경우 여러번 호출되더라도 맨 처음의 호출만 영향을 주기 때문에 computation 스케줄러에서 데이터 흐름이 발생되고 , 그 후 observeOn(Schedulers.newThread())에 의해 연산이 new thread에서 실행된다. </p>
<pre><code>main | doOnSubscribe | 
RxComputationThreadPool-1 | doOnNext | MyShape{color=&#39;Red&#39;, shape=&#39;Ball&#39;}
RxComputationThreadPool-1 | doOnNext | MyShape{color=&#39;Green&#39;, shape=&#39;Ball&#39;}
RxComputationThreadPool-1 | doOnNext | MyShape{color=&#39;Blue&#39;, shape=&#39;Ball&#39;}
RxNewThreadScheduler-1 | map(Square) | MyShape{color=&#39;Red&#39;, shape=&#39;Square&#39;}
RxNewThreadScheduler-1 | map(Square) | MyShape{color=&#39;Green&#39;, shape=&#39;Square&#39;}
RxNewThreadScheduler-1 | map(Square) | MyShape{color=&#39;Blue&#39;, shape=&#39;Square&#39;}
RxNewThreadScheduler-2 | map(Triangle) | MyShape{color=&#39;Red&#39;, shape=&#39;Triangle&#39;}
RxNewThreadScheduler-2 | map(Triangle) | MyShape{color=&#39;Green&#39;, shape=&#39;Triangle&#39;}
RxNewThreadScheduler-2 | map(Triangle) | MyShape{color=&#39;Blue&#39;, shape=&#39;Triangle&#39;}
RxNewThreadScheduler-3 | subscribe | MyShape{color=&#39;Red&#39;, shape=&#39;Triangle&#39;}
RxNewThreadScheduler-3 | subscribe | MyShape{color=&#39;Green&#39;, shape=&#39;Triangle&#39;}
RxNewThreadScheduler-3 | subscribe | MyShape{color=&#39;Blue&#39;, shape=&#39;Triangle&#39;}</code></pre><h2 id="2scheduler-종류">2.Scheduler 종류</h2>
<p>Scheduler의 종류로는 single, computation, io, trampoline, new_thread가 존재하고,
RxJava에서는 이 중 Computation, IO, Trampoline 세 가지의 Scheduler를 권장합니다.</p>
<pre><code class="language-java">public final class Schedulers {

    @NonNull
    static final Scheduler SINGLE;

    @NonNull
    static final Scheduler COMPUTATION;

    @NonNull
    static final Scheduler IO;

    @NonNull
    static final Scheduler TRAMPOLINE;

    @NonNull
    static final Scheduler NEW_THREAD;
}</code></pre>
<table>
<thead>
<tr>
<th>Scheduler</th>
<th>생성 방법</th>
<th>내용</th>
</tr>
</thead>
<tbody><tr>
<td>SINGLE</td>
<td>Schedulers.single()</td>
<td>단일 스레드를 생성해 계속 재사용</td>
</tr>
<tr>
<td>COMPUTATION</td>
<td>Schedulers.computation()</td>
<td>내부적으로 스레드 풀 생성, 스레드 개수 = 프로세서 개수</td>
</tr>
<tr>
<td>IO</td>
<td>Schedulers.io()</td>
<td>필요할 때 마다 스레드를 계속 생성</td>
</tr>
<tr>
<td>TRAMPOLINE</td>
<td>Schedulers.trampoline()</td>
<td>현재 스레드에 무한한 크기의 대기 큐 생성</td>
</tr>
<tr>
<td>NEW_THREAD</td>
<td>Schedulers.newThread()</td>
<td>매번 새로운 스레드 생성</td>
</tr>
</tbody></table>
<h3 id="1-single-thread-scheduler">1. Single Thread Scheduler</h3>
<p>단일 스레드를 계속 재사용합니다. RxJava 내부에서 스레드를 별로도 생성하며, 한 번 생성된 스레드로 여러 작업을 처리한다. 비동기 처리를 지향한다면 Single 스레드 스케쥴러를 사용할 일은 거의 없다.</p>
<h3 id="2-computation-thread-scheduler">2. Computation Thread Scheduler</h3>
<p>CPU에 대응하는 계산용 스케줄러이며 IO 작업을 하지 않고 일반적인 계산/연산 작업을 할 때 사용한다.</p>
<h3 id="3-io-thread-scheduler">3. IO Thread Scheduler</h3>
<p>파일 입출력 등의 IO 작업을 하거나 네트워크 요청 처리 시에 사용하는 스케쥴러이다. Computation 스케쥴러와 다르게 필요할 때 마다 스레드를 계속 생성한다.</p>
<h3 id="4-trampoline-thread-scheduler">4. Trampoline Thread Scheduler</h3>
<p>새로운 스레드를 생성하지 않고 사용하고 있는 현재 스레드에 무한한 크기의 대기 큐를 생성한다.</p>
<h3 id="5-new-thread-scheduler">5. New Thread Scheduler</h3>
<p>다른 스케쥴러와 달리 요청을 받을 때 마다 매번 새로운 스레드를 생성한다.</p>
<p>출처
<a href="https://4z7l.github.io/2020/12/14/rxjava-5.html">https://4z7l.github.io/2020/12/14/rxjava-5.html</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Clone] 인스타그램 클론 프로젝트(4) - 사진 업로드]]></title>
            <link>https://velog.io/@minnie_dev/Clone-%EC%9D%B8%EC%8A%A4%ED%83%80%EA%B7%B8%EB%9E%A8-%ED%81%B4%EB%A1%A0-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B84</link>
            <guid>https://velog.io/@minnie_dev/Clone-%EC%9D%B8%EC%8A%A4%ED%83%80%EA%B7%B8%EB%9E%A8-%ED%81%B4%EB%A1%A0-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B84</guid>
            <pubDate>Tue, 08 Mar 2022 03:11:39 GMT</pubDate>
            <description><![CDATA[<p>이번 강의에서는 Firebase에 사진을 업로드하는 페이지를 생성하고 Firebase의 Storage에 업로드 되었는지 확인하였다.</p>
<h3 id="1-firebase-storage-사용-설정">1. Firebase Storage 사용 설정</h3>
<p><img src="https://images.velog.io/images/minnie_dev/post/9c9b61d3-1ebe-4716-b23a-03efa880e9f3/image.png" alt="">
Firebase에서 Storage로 들어가 시작하기를 누르면 다음과 같은 화면이 나온다. 프로덕션 모드에서 시작으로 체크하고 다음으로 넘어간다.
<img src="https://images.velog.io/images/minnie_dev/post/4eadc171-9480-498e-ad36-a9bfd833154e/image.png" alt="">
위치 설정은 기본으로 설정되어있는 것으로 하고 진행하였다.
<img src="https://images.velog.io/images/minnie_dev/post/aaac39ff-0a5c-47f5-ba09-2beddc93848a/image.png" alt="">
생성하고 나면 아직 파일이 없습니다라는 문구와 함께 업로드 되어있는 파일이 없는 것을 확인할 수 있다.
<img src="https://images.velog.io/images/minnie_dev/post/70f54de9-5076-4dea-a13e-e448419b0b13/image.png" alt=""></p>
<hr>
<h3 id="2-android에서-cloud-storage로-파일-업로드">2. Android에서 Cloud Storage로 파일 업로드</h3>
<p>build.gradle(Module:app)의 dependencies 부분에 다음 라이브러리를 추가해주어 사진을 업로드할 수 있는 스토리지를 추가해준다.</p>
<pre><code>implementation &#39;com.google.firebase:firebase-storage-ktx:20.0.0&#39;
</code></pre><p>사진을 업로드할 Activity와 layout을 만든다. (AddPhotoActivity.kt, activity_add_photo.xml)</p>
<p>먼저 FirebaseStorage 타입의 변수 storage와 이미지 uri를 담을 수 있는 변수 photoUri를 생성한다. photoPickIntent에 Intent(Intent.ACTION_PICK)을 이용해 선택한 이미지를 가져올 수 있도록 한다.
런처를 이용하여 액티비티가 성공했을 때(=사진을 선택햇을 때)선택한 이미지 경로가 전달된다.
photoUri에 전달받은 이미지 경로를 넣어주고 선택된 이미지를 addphoto_image에 표시해준다. 실패했을 때는 액티비티를 종료하도록 finish()를 넣어준다.</p>
<pre><code class="language-kotlin">var storage: FirebaseStorage? = null
var photoUri: Uri? = null

private lateinit var getResult: ActivityResultLauncher&lt;Intent&gt;

getResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
                result: ActivityResult -&gt;
                    if (result.resultCode == RESULT_OK) {
                        //이미지 경로 넘어옴
                        photoUri = result.data?.data
                        binding.addphotoImage.setImageURI(photoUri)
                    }else{
                        //취소버튼
                        finish()
                    }
                }


storage = FirebaseStorage.getInstance()
val photoPickIntent = Intent(Intent.ACTION_PICK)
photoPickIntent.type = &quot;image/*&quot;
getResult.launch(photoPickIntent)
</code></pre>
<p>이미지 이름이 중복되지 않도록 파일명에 날짜값을 넣어 지정한다. storageRef에 첫번째 child에는 폴더명을 넣어주고 두번째 child에는 이미지 파일명을 넣어준다.
putFile에 이미지 파일 Uri를 넣어 사진을 업로드 한다. addOnSuccessListener는 업로드 성공할 때 실행되는 리스너이므로 성공했다는 것을 알려주기 위해 토스트 팝업을 넣는다. 이 코드를 업로드 버튼을 눌렀을 때 실행되게 한다.</p>
<pre><code class="language-kotlin">val timestamp = SimpleDateFormat(&quot;yyyyMMdd_HHmmss&quot;).format(Date())
val imageFileName = &quot;IMAGE_&quot;+ timestamp + &quot;_.png&quot;

val storageRef = storage?.reference?.child(&quot;images&quot;)?.child(imageFileName)

//파일 업로드
storageRef?.putFile(photoUri!!)?.addOnSuccessListener {
    Toast.makeText(this,getString(R.string.upload_success),Toast.LENGTH_LONG).show()
}</code></pre>
<hr>
<h3 id="3-사진-경로-가져올-수-있는-권한">3. 사진 경로 가져올 수 있는 권한</h3>
<p>MainActivity의 onCreate부분에서 아래 코드를 추가해 사진 경로를 가져올 수 있는 권한을 요청하고 onNavigationItemSelected의 해당 item에서 외부 스토리지 경로를 가져올 수 있는 권한이 있는지 체크해주고 권한이 있다면 위에서 생성한 업로드 하는 Activity로 이동시켜준다.</p>
<pre><code class="language-kotlin">ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE),1)

override fun onNavigationItemSelected(item: MenuItem): Boolean {
    when (item.itemId) {
        R.id.action_add_photo -&gt; {     
            if(ContextCompat.checkSelfPermission(this,Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED){
                 startActivity(Intent(this,AddPhotoActivity::class.java)) 
            }
            return true
       }
}
</code></pre>
<p>그리고 AndroidManifest에서 다음과 같은 권한을 설정해준다.</p>
<pre><code>&lt;uses-permission android:name=&quot;android.permission.READ_EXTERNAL_STORAGE&quot;/&gt;</code></pre><p>실행시켜보면 맨 처음에 앱에 권한을 허용하는 허용하지 않는지 물어본다. 사진 및 미디어를 사용하기 위해 Allow해준다.
<img src="https://images.velog.io/images/minnie_dev/post/7a173ac7-a143-4bed-88ea-672c1baee902/image.png" alt=""></p>
<p>➕ 에뮬레이터에 사진을 넣어주기 위해 아래 링크를 참고하여 이미지를 넣어주었다. 
<a href="https://planactor.tistory.com/324">https://planactor.tistory.com/324</a></p>
<p>하단의 네비게이션바에서 가운데 있는 갤러리로 들어가보면 다음과 같이 사진을 선택할 수 있는 창이 뜬다.
<img src="https://images.velog.io/images/minnie_dev/post/5d37ac23-a939-491f-a515-cab83ad9e38d/image.png" alt=""></p>
<p>원하는 사진을 선택해주고 업로드를 누르면 사진이 업로드가 된다.
<img src="https://images.velog.io/images/minnie_dev/post/cc900728-60ab-4cc1-90ea-3ab4ef322a11/image.png" alt=""></p>
<p>❌ 하지만 업로드버튼을 눌러도 사진이 업로드 되지않고 다음과 같은 오류가 발생하였다.</p>
<pre><code>W/NetworkRequest: No App Check token for request.
E/StorageException: StorageException has occurred.
    User does not have permission to access this object.
     Code: -13021 HttpResult: 403
E/StorageException: The server has terminated the upload session</code></pre><p>원인을 찾아보니 Storage의 Rules를 수정해주어야한다. 기본 설정은 allow read, write: if false; 라고 되어있을 것이다. 이 부분에서  false를 true로 변경시켜주면 모든 사용자에게 권한을 주는 것이고 request.auth != null; 이렇게 변경시켜주면 로그인 한 사용자에게 권한을 주는 것이다.</p>
<hr>
<p>이제 Firebase의 Storage에 들어가보면 다음과 같이 폴더가 생기고 이미지 파일이 업로드 되어있을 것 이다.</p>
<p><img src="https://images.velog.io/images/minnie_dev/post/40eb682a-8804-4f6b-90a0-b39d36055dd2/image.png" alt=""></p>
<p><img src="https://images.velog.io/images/minnie_dev/post/bd35adf1-64d9-42f6-90c3-b18d85283865/image.png" alt=""></p>
<p>참고
<a href="https://firebase.google.com/docs/storage/android/upload-files?hl=ko">https://firebase.google.com/docs/storage/android/upload-files?hl=ko</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Clone] 인스타그램 클론 프로젝트(3) - BottomNavigationView]]></title>
            <link>https://velog.io/@minnie_dev/Clone-%EC%9D%B8%EC%8A%A4%ED%83%80%EA%B7%B8%EB%9E%A8-%ED%81%B4%EB%A1%A0-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B83</link>
            <guid>https://velog.io/@minnie_dev/Clone-%EC%9D%B8%EC%8A%A4%ED%83%80%EA%B7%B8%EB%9E%A8-%ED%81%B4%EB%A1%A0-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B83</guid>
            <pubDate>Tue, 08 Mar 2022 02:34:40 GMT</pubDate>
            <description><![CDATA[<p>이번 강의에서는 아래의 사진과 같이 하단의 네비게이션 탭바를 만들었다.</p>
<p><img src="https://images.velog.io/images/minnie_dev/post/100d1567-40a7-4f0b-bbba-562eb3f582a8/image.png" alt=""></p>
<blockquote>
<p><strong>BottomNavigationView</strong>란 보통 화면 맨 밑이나 위에 붙어있는 버튼 모음으로 프래그먼트를 활용해서 각각 다른 화면들을 보여줄 때 사용한다. 하단의 탭으로 화면을 이동할 수 있는 이 기능은 카카오톡, 유튜브, 인스타그램 등 여러 앱에서 쉽게 볼 수 있다.</p>
</blockquote>
<h3 id="1-menu-xml-작성">1. menu xml 작성</h3>
<p><img src="https://images.velog.io/images/minnie_dev/post/1a08b82e-1947-423f-9663-3914608246f6/image.png" alt=""></p>
<p>res폴더 안에 menu폴더를 생성하고 BottomNavigaitonView에서 사용할 Menu Resource File을 만든다.</p>
<pre><code>&lt;menu xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;&gt;
    &lt;item
        android:id=&quot;@+id/action_home&quot;
        android:icon=&quot;@drawable/ic_home&quot;
        android:enabled=&quot;true&quot;
        android:title=&quot;home&quot;/&gt;

    &lt;item
        android:id=&quot;@+id/action_search&quot;
        android:icon=&quot;@drawable/ic_search&quot;
        android:enabled=&quot;true&quot;
        android:title=&quot;search&quot;/&gt;    

        &lt;!-- 더 추가--&gt;

&lt;/menu&gt;</code></pre><p><strong>icon</strong>속성은 탭 아이콘을 설정, <strong>title</strong>속성은 탭 이름, <strong>enabled</strong>속성은 터치 여부로 default가 true이기 때문에 추가하지 않아도 된다</p>
<h3 id="2-xml에-bottomnavigationview-추가">2. xml에 BottomNavigationView 추가</h3>
<p><strong>menu</strong>속성으로 위에서 만든 menu.xml 파일을 설정해준다. 
<strong>itemBackground</strong>속성은 탭 background를 변경, <strong>itemIconTint</strong>속성은 아이템 색상을 설정, <strong>itemTextColor</strong>속성은 탭 이름 색상을 설정한다.</p>
<pre><code>&lt;com.google.android.material.bottomnavigation.BottomNavigationView
    android:id=&quot;@+id/bottom_navigation&quot;
    android:layout_width=&quot;match_parent&quot;
    android:layout_height=&quot;wrap_content&quot;
    app:layout_constraintBottom_toBottomOf=&quot;parent&quot;
    app:menu = &quot;@menu/bottom_navigation_main&quot;
    app:itemBackground=&quot;@color/white&quot;/&gt;
</code></pre><h3 id="3-fragment-생성">3. Fragment 생성</h3>
<p>네비게이션 시 이동할 화면들을 Fragment로 생성해준다.
<img src="https://images.velog.io/images/minnie_dev/post/3b4222b2-a60f-4094-ad78-280e09bb6693/image.png" alt=""></p>
<h3 id="4-activity에서-navigationbarviewonitemselectedlistener-오버라이딩">4. Activity에서 NavigationBarView.OnItemSelectedListener 오버라이딩</h3>
<p>강의에서는 setOnNavigationItemSelectedListener을 사용했지만 현재는 deprecated 되었다고 한다. 
setOnNavigationItemSeletedListener 대신에 <strong>NavigationBarView.OnItemSelectedListener</strong>을 통해서 selecte 이벤트를 완성하였다.
아래 링크에 보다 정확한 내용이 작성되어있다. 
<a href="https://junyoung-developer.tistory.com/153">https://junyoung-developer.tistory.com/153</a></p>
<pre><code class="language-kotlin">class MainActivity : AppCompatActivity(), NavigationBarView.OnItemSelectedListener {

   override fun onCreate(savedInstanceState: Bundle?) {
        ...
        binding.bottomNavigation.setOnItemSelectedListener(this)
    }

        override fun onNavigationItemSelected(item: MenuItem): Boolean {
        when (item.itemId) {

            R.id.action_home -&gt; {
                val detailViewFragment = DetailViewFragment()
                supportFragmentManager.beginTransaction()
                    .replace(R.id.main_content, detailViewFragment).commit()
                return true
            }

            R.id.action_search -&gt; {
                val gridFragment = GridFragment()
                supportFragmentManager.beginTransaction()
                    .replace(R.id.main_content, gridFragment).commit()
                return true
            }

            ...

        }
        return false
    }


}
</code></pre>
<p>전환할 프래그먼트를 만든 뒤 FragmentManager의 replace() 함수를 통해 화면을 전환해주면 된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Clone] 인스타그램 클론 프로젝트(2) - 구글 계정 로그인]]></title>
            <link>https://velog.io/@minnie_dev/Clone-%EC%9D%B8%EC%8A%A4%ED%83%80%EA%B7%B8%EB%9E%A8-%ED%81%B4%EB%A1%A0-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B82</link>
            <guid>https://velog.io/@minnie_dev/Clone-%EC%9D%B8%EC%8A%A4%ED%83%80%EA%B7%B8%EB%9E%A8-%ED%81%B4%EB%A1%A0-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B82</guid>
            <pubDate>Thu, 03 Mar 2022 07:52:49 GMT</pubDate>
            <description><![CDATA[<p>이전 강의에서 email로 로그인할 때는 이메일로 요청하게 되면 파이어베이스 서버에서 바로 응답하는 2단계로 이루어진 구조였다. (<strong>email login in firebase-&gt; response of login</strong>)</p>
<p>하지만 소셜로그인의 경우 예로 구글 로그인의 경우 <strong>google login-&gt; firebase-&gt; response of login</strong> 3단계의 구조를 거친다. </p>
<p>구글로그인의 경우 로그인에 성공하면 구글로부터 IdToken을 전달받는다. IdToken으로 Firebase 사용자 인증 정보를 교환 받고 이 정보를 사용해 Firebase에 인증한다.</p>
<h3 id="1-firebase-console에-sha-1-지정">1. Firebase Console에 SHA-1 지정</h3>
<p>먼저 Firebase Console의 설정페이지에서 SHA-1을 지정한다.
<img src="https://images.velog.io/images/minnie_dev/post/e0ab3471-98f0-42d6-8a61-4777ac2994b5/image.png" alt="">
SHA-1을 알아내는 간단한 방법은 Gradle-&gt;Execute Gradle Task에서 signing Report라고 입력하면 하단에서 다음과 같은 정보들을 얻을 수 있다.</p>
<p><img src="https://images.velog.io/images/minnie_dev/post/0095c1f0-d799-4e46-b212-d30212c63e4f/image.png" alt=""></p>
<p><img src="https://images.velog.io/images/minnie_dev/post/d7d34372-9923-4490-94e3-9cd9ae52cb43/image.png" alt=""></p>
<h3 id="2-firebase-console-google-활성화">2. Firebase Console Google 활성화</h3>
<p><img src="https://images.velog.io/images/minnie_dev/post/34d2e88f-3933-4b73-b40f-88100c5b3337/image.png" alt=""></p>
<p>Authentication-&gt; Sign-in method 에서 Google 사용설정 활성화를 시켜준다. 프로젝트 지원 이메일은 Firebase에 로그인 되어있는 이메일로 하였다.</p>
<h3 id="3-구글로그인에-필요한-라이브러리-추가">3. 구글로그인에 필요한 라이브러리 추가</h3>
<p>build.gradle에 구글 로그인에 필요한 라이브러리들을 추가한다.</p>
<pre><code>plugins {
    id &#39;com.google.gms.google-services&#39;
}

dependencies {
    implementation platform(&#39;com.google.firebase:firebase-bom:29.1.0&#39;)

    implementation &#39;com.google.firebase:firebase-auth-ktx:21.0.1&#39;
    implementation &#39;com.google.android.gms:play-services-auth:20.1.0&#39;
}</code></pre><h3 id="4-googlesigninclient-개체-구성">4. GoogleSignInClient 개체 구성</h3>
<p>로그인 활동의 onCreate 메서드에서 앱에 필요한 사용자 데이터를 요청하도록 구글 로그인을 구성한다. 예를 들어 사용자 ID 및 기본 프로필 정보를 요청하도록 구글 로그인을 구성하려면 DEFAULT_SIGH_IN 매개변수를 사용하여 GoogleSignInOptions 개체를 만든다. 사용자의 이메일 주소도 요청하려면 requestEmail 옵션을 사용하여 GoogleSignInOptions 개체를 만들면 된다.
구글 로그인을 앱에 통합하고 GoogleSignInOptions을 구성할 때 requestIdToken을 호출한다.</p>
<p>그리고 나서 지정한 GoogleSignInOptions을 사용하여 GoogleSignInClient 개체를 만든다.</p>
<pre><code class="language-kotlin">val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
        .requestIdToken(BuildConfig.web_client_id)
        .requestEmail()
        .build()

var googleSignInClient:GoogleSignInClient? = null

googleSignInClient = GoogleSignIn.getClient(this,gso)</code></pre>
<h3 id="5-구글-로그인-화면으로-이동">5. 구글 로그인 화면으로 이동</h3>
<p>startActivityForResult()가 deprecated 되면서 registerForActivityResult()를 사용했다.</p>
<p>로그인에 성공하게 되면 구글에서는 로그인한 사용자의 정보를 얻을 때 필요한 IdToken을 전달한다. </p>
<pre><code class="language-kotlin">lateinit var getResult : ActivityResultLauncher&lt;Intent&gt;

getResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()){
                result:ActivityResult-&gt;
    if(result.resultCode == RESULT_OK) {
        val task = GoogleSignIn.getSignedInAccountFromIntent(result.data)
        try {
            val account = task.getResult(ApiException::class.java)
            firebaseAuthWithGoogle(account.idToken)
            Log.d(&quot;GoogleLogin&quot;, &quot;firebaseAuthWithGoogle: &quot; + account.id)
        } catch (e: ApiException) {
            Log.d(&quot;GoogleLogin&quot;, &quot;Google sign in failed: &quot; + e.message)
        }
    }
}


binding.googleSignInButton.setOnClickListener {
    val signInIntent = googleSignInClient?.signInIntent // 구글 로그인 화면 Intent
    getResult.launch(signInIntent)
}</code></pre>
<h3 id="6-idtoken을-활용해-firebase-인증하기">6. IdToken을 활용해 Firebase 인증하기</h3>
<p>IdToken으로 Firebase 사용자 인증 정보로 교환을 한 후 교환된 정보를 이용해 Firebase에 인증할 수 있다.</p>
<p>위에서 result코드가 RESULT_OK일 때 firebaseAuthWithGoogle(account.idToken)에 대한 코드이다. 아래에서 account 변수는 위에서 얻은 IdToken을 말한다.
Firebase 사용자 인증 정보(credential)을 사용해 Firebase에 인증한다.</p>
<pre><code class="language-kotlin">val credential = GoogleAuthProvider.getCredential(account,null)
    auth?.signInWithCredential(credential) 
        ?.addOnCompleteListener { task -&gt;
            if (task.isSuccessful) {
                //아이디 패스워드 맞았을 때
                moveMainPage(task.result?.user)
            } else {
                //로그인 실패 틀렸을 때
                Toast.makeText(this,task.exception?.message, Toast.LENGTH_LONG).show()
           }
    }
</code></pre>
<p>참고
<a href="https://developers.google.com/identity/sign-in/android/sign-in?authuser=0">https://developers.google.com/identity/sign-in/android/sign-in?authuser=0</a></p>
<p><a href="https://velog.io/@galaxy/Firebase-Authentication%EC%9D%84-%ED%99%9C%EC%9A%A9%ED%95%9C-Google-Login">https://velog.io/@galaxy/Firebase-Authentication%EC%9D%84-%ED%99%9C%EC%9A%A9%ED%95%9C-Google-Login</a></p>
<p><a href="https://hanyeop.tistory.com/160">https://hanyeop.tistory.com/160</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Clone] Firebase 연동]]></title>
            <link>https://velog.io/@minnie_dev/Clone-Firebase</link>
            <guid>https://velog.io/@minnie_dev/Clone-Firebase</guid>
            <pubDate>Thu, 24 Feb 2022 07:18:50 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>FireBase</strong>에서는 인증을 통해 로그인을 담당할 수 있다. 로그인을 담당하는 부분은 직접 서버로 개발할 경우 매우 복잡하다. 그 이유는 인증된 사용자인지, 아닌지를 확인하는 세션처리에서 그 세션으로 데이터베이스, 저장소에 접근해도 문제가 없는지 확인하는 보안처리, 비밀번호 찾기, 아이디 찾기, 비밀번호 바꾸기, 이메일 인증 등등 복잡한 것을 구축해야 한다. 하지만 FireBase는 이 모든 것들을 지원한다.</p>
</blockquote>
<h3 id="1-android-studio에서-tools--firebase-authenticate">1. Android Studio에서 Tools -&gt;Firebase Authenticate</h3>
<p align="center"><img src="https://images.velog.io/images/minnie_dev/post/551cb925-3dff-461c-aafb-62c36c406070/image.png" width ="500" height ="300"></p>

<p>AndroidStudio에서 Tools-&gt;Firebase로 이동하면 스튜디오의 오른쪽 부분에 다음과 같은 뜬다.
그 중에서 <strong>Authenticate</strong>를 눌러보면 강의에서는 Email and password Authenticate가 있었지만 여기에서는 Authenticat using Google Sign-in, Authenticate using Facebook Login, Authenticate using a custom authentication system 등이 있다. 여기서 <strong>Authenticate using a custom authentication system</strong>로 들어갔다. </p>
<h3 id="2-firebase와-연결하기">2. Firebase와 연결하기</h3>
<p align="center"><img src="https://images.velog.io/images/minnie_dev/post/cdf47985-1659-4f06-8c4b-f730245fc43a/image.png" height ="400dpx" width="600px"></p>

<p>먼저 <strong>Connect to Firebase</strong>를 눌러보면 아래와 같은 페이지로 넘어가게 된다.(구글 계정으로 로그인이 되어있을때) 프로젝트 추가를 눌러 새로운 프로젝트를 생성한다.</p>
<p><img src="https://images.velog.io/images/minnie_dev/post/e61b55df-5204-47c0-8677-714b13a95cf5/image.png" alt=""></p>
<table>
<thead>
<tr>
<th align="left">1단계</th>
<th align="left">2단계</th>
<th align="left">3단계</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><img src="https://images.velog.io/images/minnie_dev/post/6a32b7e7-a4a5-40a3-9942-7cacc8137a27/image.png" alt=""></td>
<td align="left"><img src="https://images.velog.io/images/minnie_dev/post/cabe7e84-f07e-4b26-93f6-5eb2a9dc48d5/image.png" alt=""></td>
<td align="left"><img src="https://images.velog.io/images/minnie_dev/post/6dbb8351-539b-4978-81b3-288da4082035/image.png" alt=""></td>
</tr>
</tbody></table>
<p>위의 3단계를 다 거치고 나면 새 프로젝트가 준비되었다는 안내와 Firebase에 Firebase Android 앱이 생성되었다는 창이 뜬다.</p>
<p><img src="https://images.velog.io/images/minnie_dev/post/09d93058-6ed1-4582-94a5-a95b02a6cea8/image.png" alt=""></p>
<p>연결 버튼을 누르게 되면 Firebase와 연결이 완료된 것이다. 안드로이드 스튜디오에서 Conntect to Firebase 버튼에서 Connected라는 문구로 변경되었는지 확인한다.</p>
<p><img src="https://images.velog.io/images/minnie_dev/post/3826c6ea-4305-418c-9c09-492e19350f8d/image.png" alt=""></p>
<h3 id="3-firebase-인증-sdk-앱에-추가하기">3. Firebase 인증 SDK 앱에 추가하기</h3>
<p><img src="https://images.velog.io/images/minnie_dev/post/1ab2e578-0877-46d2-9a5f-99cdc12877dd/image.png" alt=""></p>
<p><strong>Add the Firebase Authentication SDK to your app</strong> 버튼을 누르면 다음과 같은 창이 뜬다. Accept Changes를 누르면 build.gradle에 다음과 같은 내용들이 추가되게 된다.</p>
<p><img src="https://images.velog.io/images/minnie_dev/post/8f33ea26-38a3-4564-849e-561ed91e06dd/image.png" alt=""></p>
<p>SDK 추가가 완료되면 <strong>Dependencies set up correctly</strong> 문구로 변경되게된다.</p>
<p><img src="https://images.velog.io/images/minnie_dev/post/aaf1f27e-62e8-4f29-aa03-9df9ca8406c8/image.png" alt=""></p>
<h3 id="4-로그인방법-추가">4. 로그인방법 추가</h3>
<p><img src="https://images.velog.io/images/minnie_dev/post/76b333b6-30b4-48dd-810c-c9368c3438cd/image.png" alt=""></p>
<p>해당 콘솔로 들어가 <strong>Authentication의 Sign-in method</strong> 메뉴에 들어가면 로그인 방법을 추가할 수 있다. 우선 이메일/비밀번호 방법을 활성화 시킨다.</p>
<p><img src="https://images.velog.io/images/minnie_dev/post/b0ca4847-e095-4673-8b1e-49dd812f0dfc/image.png" alt=""></p>
<p><img src="https://images.velog.io/images/minnie_dev/post/d2f8e2ee-bdac-49c2-8cfe-271a4f28f1e0/image.png" alt=""></p>
<p>위와 같은 설정을 완료하게 되면 Firebase를 통해 로그인 기능을 사용할 수 있게 된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Clone] 인스타그램 클론 프로젝트(1) - 로그인화면, Firebase 연결]]></title>
            <link>https://velog.io/@minnie_dev/Clone-%EC%9D%B8%EC%8A%A4%ED%83%80%EA%B7%B8%EB%9E%A8-%ED%81%B4%EB%A1%A0-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B81</link>
            <guid>https://velog.io/@minnie_dev/Clone-%EC%9D%B8%EC%8A%A4%ED%83%80%EA%B7%B8%EB%9E%A8-%ED%81%B4%EB%A1%A0-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B81</guid>
            <pubDate>Wed, 23 Feb 2022 06:48:58 GMT</pubDate>
            <description><![CDATA[<h2 id="인스타그램-클론-프로젝트">인스타그램 클론 프로젝트</h2>
<blockquote>
<p><strong>클론 프로젝트</strong>란 이미 개발되어 있는 서비스를 주제 삼아 서비스의 기존 스택이나 새로운 스택을 적용하여 나만의 서비스로 복제하듯 개발해보는 것을 말한다. 이미 개발되어 있는 서비스를 통해 개발에만 집중할 수 있다는 장점이 있다. </p>
</blockquote>
<p>인프런을 찾아보다가 무료로 인스타그램 클론 프로젝트에 대한 강의가 있는 것을 보고 바로 수강하기로 했다.</p>
<p><a href="https://www.inflearn.com/course/%EC%9D%B8%EC%8A%A4%ED%83%80%EA%B7%B8%EB%9E%A8%EB%A7%8C%EB%93%A4%EA%B8%B0-%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C">https://www.inflearn.com/course/%EC%9D%B8%EC%8A%A4%ED%83%80%EA%B7%B8%EB%9E%A8%EB%A7%8C%EB%93%A4%EA%B8%B0-%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C</a></p>
<h2 id="1강-로그인화면-만들기">1강 로그인화면 만들기</h2>
<p><img src="https://images.velog.io/images/minnie_dev/post/ed8db278-9af9-4784-bc71-a391f7656764/image.png" alt="">
1강은 로그인 화면을 구성하는 작업이다. 강의에서는 RelativeLayout을 사용하였지만 constraintlayout을 사용하였다. 화면 구성을 하고 맨 처음 시작화면을 LoginActivity 화면으로 하기 위해서 AndroidMainfest에서 기존의 MainActivity에 있었던 intent-filter를 새로 생성한 LoginActivity쪽으로 옮겨주었다.</p>
<h2 id="2강-파이어베이스에-연결하기">2강 파이어베이스에 연결하기</h2>
<h3 id="1-파이어베이스와-앱-연결하기">1. 파이어베이스와 앱 연결하기</h3>
<p>💥<strong>Firebase와 앱과 연결하는 부분은 아래 포스트에 별도로 업로드 하였다.</strong>💥
<a href="https://velog.io/@minnie_dev/Clone-Firebase">https://velog.io/@minnie_dev/Clone-Firebase</a></p>
<h3 id="2-인증-관련-라이브러리-객체-생성">2. 인증 관련 라이브러리 객체 생성</h3>
<pre><code class="language-kotlin">var auth: FirebaseAuth? = null
auth = FirebaseAuth.getInstance()
</code></pre>
<h3 id="3-firebaseauth-기능을-통한-회원가입로그인">3. FirebaseAuth 기능을 통한 회원가입/로그인</h3>
<table>
<thead>
<tr>
<th align="left">명칭</th>
<th align="left">기능</th>
</tr>
</thead>
<tbody><tr>
<td align="left">createUserWithEmailAndPassword</td>
<td align="left">회원가입</td>
</tr>
<tr>
<td align="left">signlnWithEmailAndPassword</td>
<td align="left">로그인</td>
</tr>
<tr>
<td align="left">sendEmailVerification</td>
<td align="left">회원 가입한 이메일 유효 확인</td>
</tr>
<tr>
<td align="left">updateEmail</td>
<td align="left">회원 가입한 아이디 이메일 변경</td>
</tr>
<tr>
<td align="left">updatePassword</td>
<td align="left">회원 가입한 아이디 패스워드 변경</td>
</tr>
<tr>
<td align="left">sendPasswordResetEmail</td>
<td align="left">회원 가입한 비밀번호 재설정</td>
</tr>
<tr>
<td align="left">delete</td>
<td align="left">회원 가입한 아이디 삭제</td>
</tr>
<tr>
<td align="left">reauthenticate</td>
<td align="left">아이디 재 인증</td>
</tr>
</tbody></table>
<p><img src="https://images.velog.io/images/minnie_dev/post/bcc003e9-44ce-4eb1-8632-63873465620f/image.png" alt=""></p>
<p>FirebaseAuth에는 다양한 기능들이 있다. 그 중에서 회원가입 기능과 로그인 기능을 사용해보았다. 빈칸에 이메일과 비밀번호를 적고 만약 기존에 없었던 이메일이라면 <strong>createUserWithEmailAndPassword</strong>를 통해 회원가입을 하게 되고, 있었던 이메일이라면 <strong>signInWithEmailAndPassword</strong>를 통해 로그인을 하게된다.</p>
<pre><code class="language-kotlin">private fun signInAndSignUp() {
    auth?.createUserWithEmailAndPassword(
        binding.emailEdittext.text.toString(),
        binding.passwordEdittext.text.toString()
        )?.addOnCompleteListener { task -&gt;
            when {
                task.isSuccessful -&gt; { // id 생성 성공
                    moveMainPage(task.result?.user) }
                else -&gt; { // 회원가입도 아니고 에러도 아니기 때문에 로그인 성공
                    signInEmail() }
            }
       }
}

private fun signInEmail() {
    auth?.signInWithEmailAndPassword(
        binding.emailEdittext.text.toString(),
        binding.passwordEdittext.text.toString()
    )?.addOnCompleteListener { task -&gt;
                if (task.isSuccessful) { //로그인 성공
                    moveMainPage(task.result?.user)
                } else { //로그인 실패
                    Toast.makeText(this,task.exception?.message, Toast.LENGTH_LONG).show()
                }
            }
    }</code></pre>
<h3 id="4-회원가입-또는-로그인에-성공하면-화면-전환">4. 회원가입 또는 로그인에 성공하면 화면 전환</h3>
<p>user가 null이 아닐 때 화면을 전환할 수 있도록한다.</p>
<pre><code class="language-kotlin">private fun moveMainPage(user:FirebaseUser?){
    if(user!=null)
        startActivity(Intent(this,MainActivity::class.java))
}</code></pre>
<p>❌ 처음에 임시로 로그인을 하였을 때 다음과 같은 오류가 발생하고 계정이 생성되지 않았다. </p>
<p><span style="color:red"><strong>Ignoring header X-Firebase-Locale because its value was null.</strong></span ></p>
<p>알고보니 비밀번호를 너무 짧게 설정해서 뜬 오류였다. 비밀번호를 길게하여 다시 생성해보니 정상적으로 동작하는 것을 확인할 수 있었다.</p>
<p>❌ <span style="color:red"><strong>system ui isn&#39;t responding</strong></span ></p>
<p>에뮬레이터로 실행하다보니 아래와 같은 오류가 종종 발생하였다. 아래 링크를 통해 문제를 해결할 수 있었다.</p>
<p><a href="https://mmol.tistory.com/entry/%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-System-UI-isnt-responding-100-%ED%95%B4%EA%B2%B0%EB%B2%95">https://mmol.tistory.com/entry/%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-System-UI-isnt-responding-100-%ED%95%B4%EA%B2%B0%EB%B2%95</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[RxJava] RxJava 조건, 수학, 기타 연산자]]></title>
            <link>https://velog.io/@minnie_dev/RxJava-Observable6</link>
            <guid>https://velog.io/@minnie_dev/RxJava-Observable6</guid>
            <pubDate>Wed, 23 Feb 2022 05:38:48 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>📝RxJava의 학습 순서</strong></p>
</blockquote>
<ol>
<li>Observable 클래스를 명확하게 이해(특히 Hot Observable과 Cold Observable의 개념을 꼭 이해해야함)
<span style ="color:blue"><strong>2. 변환, 제어, 결합 연산자 등 카테고리별 주요 함수 공부</strong></span></li>
<li>스케줄러의 의미, subscribeOn()과 observeOn()함수의 차이</li>
<li>그 밖의 디버깅, 흐름 제어함수</li>
</ol>
<hr>
<h2 id="1-조건-연산자">1. 조건 연산자</h2>
<p>조건 연산자는 Observable 객체 내 데이터의 흐름을 제어하는 연산자이다. 우리는 이와 비슷한 연산자로 filter 연산자를 볼 수 있다. filter 연산자는 Observable 객체 내 데이터에서 원하는 조건에 부합하는 데이터를 발행하고, 미부합하는 데이터는기각하는 연산이 목적이었다면 조건연산자는 데이터의 발행 여부 보다는 그 흐름을 제어하는 연산자이다.</p>
<h3 id="1-1-amb">1-1. amb()</h3>
<blockquote>
<p>amb 연산자는 여러 개 들어오는 Observable 중에서 가장 먼저 데이터가 발행되는 것을 선택하는 조건 연산자이다. </p>
</blockquote>
<p><img src="https://images.velog.io/images/minnie_dev/post/890b6926-8ace-4d42-9262-cce24ba3748f/image.png" alt=""></p>
<h3 id="1-2-takeuntil">1-2. takeUntil()</h3>
<blockquote>
<p>takeUntil 연산자는 사전에 먼저 들어온 Observable 객체를 발행하고 구독하고 있는 상태에서 인자로 넘겨 받은 Observable이 발행되면 먼저 발행되었던 Observable 객체를 무시하고 인자로 받은 Observable을 발행한 다음 작업을 마치는 연산자이다. Observable 객체의 값이 들어오면 다른 데이터의 발행이 완료되지 않아도 바로완료 이벤트를 나타낸다.</p>
</blockquote>
<p><img src="https://images.velog.io/images/minnie_dev/post/838eab3f-c144-4849-9b3d-f88c40bd126c/image.png" alt=""></p>
<h3 id="1-3-skipuntil">1-3. skipUntil()</h3>
<blockquote>
<p>skipUntil 연산자는 takeUntil 연산자와는 정반대로 인자로 주어진 Observable을 발행한 다음부터 기존에 주어진 Observable 객체의 데이터를 발행하겠다는 연산자이다.</p>
</blockquote>
<p><img src="https://images.velog.io/images/minnie_dev/post/8dcf318f-e843-4b00-84df-004aa111dd98/image.png" alt=""></p>
<h3 id="1-4-all">1-4. all()</h3>
<blockquote>
<p>all 연산자는 주어진 모든 조건을 부합하는 Observable을 발행하는 연산자이다.</p>
</blockquote>
<p><img src="https://images.velog.io/images/minnie_dev/post/22beb1c6-b9ee-4c73-8e7f-74befd127072/image.png" alt=""></p>
<hr>
<h2 id="2-수학-연산자">2. 수학 연산자</h2>
<p>수학 연산자는 여러 개의 숫자형 데이터로 이루어진 Array 등을 이용하여 합, 평균 등의 계산을 수행하여 하나의 Observable 객체로 만들어 주는연산자이다.
RxJava에서 수학 연산자는 1.0에서 지원했었던 의존성이였다. 따라서 Math 관련된 연산자는 별도의 의존성을 추가해야한다.</p>
<pre><code>implementation &quot;io.reactivex.rxjava-math:1.0.0&quot;</code></pre><p>참고 : <a href="https://blog.neonkid.xyz/265?category=486400">https://blog.neonkid.xyz/265?category=486400</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[RxJava] RxJava 결합 연산자]]></title>
            <link>https://velog.io/@minnie_dev/RxJava-Observable5</link>
            <guid>https://velog.io/@minnie_dev/RxJava-Observable5</guid>
            <pubDate>Tue, 22 Feb 2022 08:57:19 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>📝RxJava의 학습 순서</strong></p>
</blockquote>
<ol>
<li>Observable 클래스를 명확하게 이해(특히 Hot Observable과 Cold Observable의 개념을 꼭 이해해야함)
<span style ="color:blue"><strong>2. 변환, 제어, 결합 연산자 등 카테고리별 주요 함수 공부</strong></span></li>
<li>스케줄러의 의미, subscribeOn()과 observeOn()함수의 차이</li>
<li>그 밖의 디버깅, 흐름 제어함수</li>
</ol>
<hr>
<h2 id="1-결합-연산자">1. 결합 연산자</h2>
<blockquote>
<p><strong>결합 연산자</strong>는 다수의 Observable을 하나로 합치는 방법을 제공한다. flatMap(), groupBy() 함수 등은 1개의 Observable을 확장해주는 반면 결합 연산자들은 여러 개의 Observable을 내가 원하는 Observable로 결합해준다.</p>
</blockquote>
<h3 id="1-1-zip">1-1 zip()</h3>
<blockquote>
<p>zip()은 두 개 이상의 Observable 객체를 이용하여 하나의 ObservableSource로 결합해주는 연산자이다. 두 데이터가 발행되기 전의 상태라면 데이터를 받을 때까지 기다려야 한다. zip연산자는 두 연산자 중 하나의 연산자라도 발행된 상태가 아니라면 데이터가 발행될 때까지 대기한다.
최대 9개의 Observable을 결합할 수 있다.</p>
</blockquote>
<p><img src="https://images.velog.io/images/minnie_dev/post/ca936a9c-3497-4aee-ad62-01dbcc103d63/image.png" alt=""></p>
<pre><code>Observable&lt;Integer&gt; source = Observable.zip(
          Observable.just(100, 200, 300),
          Observable.just(10, 20, 30),
          Observable.just(1, 2, 3),
          (a, b, c) -&gt; a + b + c );
source.subscribe(System.out::println);</code></pre><pre><code>111
222
333</code></pre><p>➕ <strong>zipWith()</strong>
zip과 비슷한 연산자로 zipWith()가 있다. <strong>zipWith()</strong>는 기존에 반환된 단일 Observable 객체에서 또 다른 zip 연산을 취하고자 하는 경우에 사용가능하다. </p>
<pre><code>Disposable source = Observable.just(100, 200, 300).zipWith(
                Observable.just(10, 20, 30),
                 (a, b) -&gt; a + b
                ).subscribe(System.out::println);       </code></pre><pre><code>110
220
330</code></pre><h3 id="1-2-combinelatest">1-2 combineLatest()</h3>
<blockquote>
<p>combineLatest()는 두 개의 Observable에서 각각 데이터가 생성될 때 데이터를 조합해서 전달하는 연산자이다.첫 데이터는 두 개의 Observable에서 모두 데이터가 생성됐을 때 전달이 되고, 그 이후에는 각각의 Observable에서 데이터가 생산될 때 데이터를 조합해서 전달한다.</p>
</blockquote>
<p><img src="https://images.velog.io/images/minnie_dev/post/620597e0-e28b-45e5-b813-aa52aa37d08e/image.png" alt=""></p>
<h3 id="1-3-merge">1-3 merge()</h3>
<blockquote>
<p>merge()는 zip과 combineLatest와 다소 차이가 있다. 결합을 위해서는 단일 Observable이 아닌 두 개 이상의 Observable을 넣어야 한다. 하지만 <strong>merge</strong>는 순서나 데이터 발행 여부와 관계 없이 UpStream에서 먼저 입력되는 데이터를 그대로 발행한다.</p>
</blockquote>
<p><img src="https://images.velog.io/images/minnie_dev/post/9f30de2e-cc97-499e-b41d-66cf0afb7380/image.png" alt=""></p>
<h3 id="1-4-concat">1-4 concat()</h3>
<blockquote>
<p>concat 연산자는 이름 그대로 객체를 이어 붙여주는 연산자이다. 2개 이상의 Observable 객체를 이어 붙여 하나의 ObservableSource로 만들어주는 연산자이다.</p>
</blockquote>
<p><img src="https://images.velog.io/images/minnie_dev/post/1f2f2676-37e5-4ce0-af50-abed909fbe6e/image.png" alt=""></p>
<p>참고 : <a href="https://blog.neonkid.xyz/263">https://blog.neonkid.xyz/263</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[RxJava] RxJava 제어 연산자]]></title>
            <link>https://velog.io/@minnie_dev/RxJava-Observable4</link>
            <guid>https://velog.io/@minnie_dev/RxJava-Observable4</guid>
            <pubDate>Fri, 18 Feb 2022 08:00:07 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>📝RxJava의 학습 순서</strong></p>
</blockquote>
<ol>
<li>Observable 클래스를 명확하게 이해(특히 Hot Observable과 Cold Observable의 개념을 꼭 이해해야함)
<span style ="color:blue"><strong>2. 변환, 제어, 결합 연산자 등 카테고리별 주요 함수 공부</strong></span></li>
<li>스케줄러의 의미, subscribeOn()과 observeOn()함수의 차이</li>
<li>그 밖의 디버깅, 흐름 제어함수</li>
</ol>
<hr>
<h2 id="1-제어-연산자">1. 제어 연산자</h2>
<blockquote>
<p>RxJava에서 제어 연산자란 입력 데이터 중 원하는 데이터를 골라내는 함수이다.
filter(), first(), take() 등이 있다.</p>
</blockquote>
<h3 id="🧩-filter">🧩 filter()</h3>
<p><strong>filter</strong>는 Observable에서 원하는 데이터만 걸러내는 역할을 한다. 즉, 필요없는 데이터는 제거하고, 원하는 데이터만 filter()함수를 통과하게 된다.</p>
<p><img src="https://images.velog.io/images/minnie_dev/post/b434d1bf-59ee-4dd2-a10d-aa7322dc24cc/image.png" alt=""></p>
<p>objs변수에는 Observable이 발행하는 데이터가 들어가있다. filter()함수에는 짝수를 판별하는 Predicate타입의 함수로 만들어 넣어준다.
filter함수 안에는 Predicate(boolean 값을 리턴하는 함수형 인터페이스)를 인자로 넣는다.</p>
<pre><code>Integer[] nums = {10,15,76,38,29

Observable.fromArray(nums)
        .filter(num-&gt;num%2==0)
        .subscribe(System.out::println);</code></pre><pre><code>10
76
38</code></pre><h3 id="🧩-filter와-비슷한-함수들의-활용-예">🧩 filter()와 비슷한 함수들의 활용 예</h3>
<ul>
<li>first(default) : Observable의 첫번째 값을 필터. 만약에 값 없이 완료된다면 기본값을 리턴</li>
<li>last(default) : Observable의 마지막 값을 필터. 만약에 값 없이 완료된다면 기본값을 리턴</li>
<li>take(N) : 최초 N개의 값만 가져옴</li>
<li>takeLast(N) : 마지막 N개의 값만 필터</li>
<li>skip(N) : 최초 N개 값을 건너뜀</li>
<li>skipLast(N) : 마지막 N개 값을 건너뜀</li>
</ul>
<pre><code>Integer[] nums= {100,200,300,400,500};
Single&lt;Integer&gt; single;
Observable&lt;Integer&gt; source;

single = Observable.fromArray(nums).first(-1);
single.subscribe(data -&gt; System.out.println(&quot;first() value = &quot;+data));

single = Observable.fromArray(nums).last(999);
single.subscribe(data -&gt; System.out.println(&quot;last() value = &quot;+data));

source = Observable.fromArray(nums).take(3);
source.subscribe(data -&gt; System.out.println(&quot;take(3) value = &quot;+data));

source = Observable.fromArray(nums).takeLast(3);
source.subscribe(data -&gt; System.out.println(&quot;takeLast(3) value = &quot;+data));

source = Observable.fromArray(nums).skip(2);
source.subscribe(data -&gt; System.out.println(&quot;skip(2) value = &quot;+data));

source = Observable.fromArray(nums).skipLast(2);
source.subscribe(data -&gt; System.out.println(&quot;skipLast(2) value = &quot;+data));</code></pre><pre><code>first() value = 100
last() value = 500
take(3) value = 100
take(3) value = 200
take(3) value = 300
takeLast(3) value = 300
takeLast(3) value = 400
takeLast(3) value = 500
skip(2) value = 300
skip(2) value = 400
skip(2) value = 500
skipLast(2) value = 100
skipLast(2) value = 200
skipLast(2) value = 300</code></pre><p>참고 <a href="https://beomseok95.tistory.com/24">https://beomseok95.tistory.com/24</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[RxJava] RxJava 변환 연산자]]></title>
            <link>https://velog.io/@minnie_dev/RxJava-Observable3</link>
            <guid>https://velog.io/@minnie_dev/RxJava-Observable3</guid>
            <pubDate>Fri, 18 Feb 2022 07:55:14 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>📝RxJava의 학습 순서</strong></p>
</blockquote>
<ol>
<li>Observable 클래스를 명확하게 이해(특히 Hot Observable과 Cold Observable의 개념을 꼭 이해해야함)
<span style ="color:blue"><strong>2. 변환, 제어, 결합 연산자 등 카테고리별 주요 함수 공부</strong></span></li>
<li>스케줄러의 의미, subscribeOn()과 observeOn()함수의 차이</li>
<li>그 밖의 디버깅, 흐름 제어함수</li>
</ol>
<hr>
<h2 id="1-rxjava-연산자">1. RxJava 연산자</h2>
<p>RxJava 연산자(Operators)의 종류는 아래의 표로 정리하였다.</p>
<table>
<thead>
<tr>
<th align="left">연산자 종류</th>
<th>정의</th>
<th>예</th>
</tr>
</thead>
<tbody><tr>
<td align="left">생성연산자</td>
<td>Observable, Single 클래스 등을 이용하여 데이터의 흐름을 만들어내는 함수</td>
<td>create(),just(),fromXXX(),interval(),</br>range(),timer() 등</td>
</tr>
<tr>
<td align="left">변환연산자</td>
<td>입력을 받아서 원하는 출력을 내는 전통적인 의미의 함수</td>
<td>map(),flatmap(), reduce() 등</td>
</tr>
<tr>
<td align="left">제어연산자</td>
<td>입력 데이터 중 원하는 데이터를 골라내는 함수</td>
<td>filter(),first(),take() 등</td>
</tr>
<tr>
<td align="left">결합연산자</td>
<td>두 개 이상의 입력된 데이터를 하나의 데이터로 통합하는 연산자</td>
<td>zip(), combinelLatest(), Merge(),concat()</td>
</tr>
<tr>
<td align="left">오류처리연산자</td>
<td>연산자 내에서 예외 처리 구현을 위한 함수</td>
<td>onErrorReturn(), onErrorResumeNext(), retry() 등</td>
</tr>
<tr>
<td align="left">조건연산자</td>
<td>Observable의 흐름을 제어하는역할</td>
<td>amb(),takeUntil(),skipUtil(),all() 등</td>
</tr>
<tr>
<td align="left"></br>수학과 집합형 연산자 &nbsp;&nbsp;&nbsp;</td>
<td>수학 함수와 연관있는 연산자</td>
<td>sum() 등</td>
</tr>
<tr>
<td align="left">기타연산자</td>
<td>구독,발행 등의 이벤트 처리 및 데이터의 숫자를 세는 특징별 연산자</td>
<td>subscribeOn(), observeOn(),count() 등</td>
</tr>
</tbody></table>
<p>생성 연산자는 Observable 설명과 함께 작성하였으므로 변환 연산자</p>
<h2 id="2-rxjava-변환-연산자">2. RxJava 변환 연산자</h2>
<blockquote>
<p><strong>RxJava의 변환 연산자</strong>란 입력을 받아서 원하는 출력을 내는 의미의 함수로 만들어진 데이터 흐름을 원하는 대로 변형 시킬 수 있다.
<strong>ex)</strong> map(), flatmap(), cancatMap(), switchMap(), reduce(), scan()</p>
</blockquote>
<h3 id="2-1-map">2-1. map</h3>
<p><strong>map</strong>은 기본적인 데이터 변환으로 입력값을 어떤 함수에 넣어서 원하는 값으로 변환하는 함수이다. 데이터를 데이터로 변환시키는 함수로 <span style = "color:blue"><strong>데이터 한 개를 변환하여 데이터 한 개</strong></span>가 나온다.</p>
<p><img src="https://images.velog.io/images/minnie_dev/post/56c5e383-20c7-4d8d-abf9-3efbb4ff9336/image.png" alt=""></p>
<p>코드를 보면 numArr변수는 String 배열 객체이고, 이 변수를 이용해 데이터 흐름을 생성하기 때문에 초기 데이터들은 String타입의 데이터들이다.</p>
<pre><code class="language-java">String[] numArr = new String[]{&quot;1&quot;,&quot;2&quot;,&quot;3&quot;};
Observable.fromArray(numArr)
          .map(Integer::parseInt)
          .subscribe(System.out::println);</code></pre>
<pre><code>위 코드가 출력된 내용
1
2
3</code></pre><p>String형이였던 데이터들이 Int형인 데이터로 출력되는 것을 확인할수 있다.</p>
<h3 id="2-2-flatmap">2-2. flatMap</h3>
<p><strong>flatMap</strong>은 <span style = "color:blue"><strong>단일 데이터를 또 다른 데이터 흐름으로 변환하는 함수</strong></span>이다. 여기서 또 다른 데이터 흐름이란 Observable을 의미한다.
<span style = "color:red"><strong>연산 결과가 반드시 Observable로 나온다는 것이 map과 다른 점이다</strong></span>. map함수는 1:1 데이터 연산 함수라면 flatMap은 <strong>1:N, 1:1 Observable</strong>함수이다.
map은 동기 방식이기 때문에 비동기와 같이 사용할 경우 그 효과를 보기 어려우며 비동기 작업으로 데이터를 처리하고자 하는 경우 map이 아닌 flatMap을 사용해야 한다.</p>
<p><img src="https://images.velog.io/images/minnie_dev/post/d28b522b-0121-4b19-8a3d-3dc3c1b3fb20/image.png" alt=""></p>
<pre><code class="language-java">Observable.interval(100L, TimeUnit.MILLISECONDS)
          .take(3)
          .flatMap(data -&gt; Observable.interval(200L, TimeUnit.MILLISECONDS)
                        .take(2)
                        .map(val -&gt; &quot;data: &quot; + data + &quot; value: &quot; + val))
          .subscribe(System.out::println);</code></pre>
<pre><code>위 코드가 출력된 내용
data: 0 value: 0
data: 1 value: 0
data: 2 value: 0
data: 0 value: 1
data: 1 value: 1
data: 2 value: 1</code></pre><p><strong>map과 flatMap의 차이를 정리해보면 map은 1개의 데이터를 다른 값이나 타입으로 변환해주는 것이고 flatMap은 1개의 값을 받아 여러 개의 데이터(Observable)로 확장해주는 것이다.</strong></p>
<h3 id="2-3-concatmap">2-3. concatMap</h3>
<p><strong>concatMap</strong>은 flatMap과 사용법이 완전 똑같다. 다른 점은 <span style = "color:blue"><strong>flatMap과 달리 입력 데이터의 순서를 보장</strong></span>해준다는 것이다. 대신 입력 순서를 보장해주는만큼 데이터 간 기다리는 지연 시간이 생기기 때문에 총 실행 시간이 길어진다.</p>
<p><img src="https://images.velog.io/images/minnie_dev/post/a52bc69d-4ddf-4931-bf0c-f6d116ee0664/image.png" alt=""></p>
<pre><code class="language-java">Observable.interval(100L, TimeUnit.MILLISECONDS)
                .take(3)
                .concatMap(data -&gt; Observable.interval(200L, TimeUnit.MILLISECONDS)
                        .take(2)
                        .map(val -&gt; &quot;data: &quot; + data + &quot; value: &quot; + val))
                .subscribe(System.out::println);</code></pre>
<pre><code>위 코드가 출력된 내용
data: 0 value: 0
data: 0 value: 1
data: 1 value: 0
data: 1 value: 1
data: 2 value: 0
data: 2 value: 1</code></pre><h3 id="2-4-switchmap">2-4. switchMap</h3>
<p><strong>switchMap</strong>은 가장 최신 데이터를 보장하며 데이터 흐름을 생성한다. 
flatMap과 concatMap과 동일하게 모든 데이터 입력이 들어오면 새로운 데이터 흐름으로 변환하려고 시도한다. 그런데 <span style= "color:blue"><strong>변환 작업 중 새로운 데이터 입력이 들어오게 되면 기존 작업을 취소하고 새로운 데이터에 대해서 변환 작업을 진행한다.</strong></span> 변환 도중 새로운 입력이 들어오게 되어 기존 작업을 취소하게 되고 최종적으로 마지막 입력 데이터에 대해서 변환을 진행한다. 이러한 특성은 <strong>실시간으로 최신 데이터를 가져올 때 유용하게 쓰인다.</strong></p>
<p><img src="https://images.velog.io/images/minnie_dev/post/df9eff80-ecef-4d6f-ba53-179981273599/image.png" alt=""></p>
<pre><code class="language-java">Observable.interval(100L, TimeUnit.MILLISECONDS)
          .take(3)
          .switchMap(data -&gt; Observable.interval(200L, TimeUnit.MILLISECONDS)
                                       .take(2)
                                       .map(val -&gt; &quot;data: &quot; + data + &quot; value: &quot; + val))
          .subscribe(System.out::println);</code></pre>
<pre><code>위 코드가 출력된 내용
data: 2 value: 0
data: 2 value: 1</code></pre><h3 id="2-5-reduce">2-5. reduce</h3>
<p><strong>reduce</strong>는 모든 데이터를 조합(변환)하여 최종 값을 출력한다. <span style = "color:blue"><strong>데이터를 중첩되게 변환시켜 최종적인 새로운 변환된 단일 데이터를 만든다.</strong></span></p>
<p><img src="https://images.velog.io/images/minnie_dev/post/e9ae21b4-52ab-47d1-bbc4-af6df0cff327/image.png" alt=""></p>
<pre><code class="language-java">String[] balls = new String[] {&quot;A&quot;, &quot;B&quot;, &quot;C&quot;};

Observable.fromArray(balls)
          .reduce((ball1, ball2) -&gt; ball2 + &quot;(&quot; + ball1 + &quot;)&quot;)
          .subscribe(System.out::println);</code></pre>
<pre><code>위 코드가 출력된 내용
C(B(A))</code></pre><p>데이터로 변환된 값이 구독자에게 전달되는 것이 아닌 다시 reduce함수의 입력값으로 전달되어 새로운 변환된 데이터를 생성하고 다시 입력값으로 들어가는 이러한 과정이 데이터 흐름의 onComplete() 이벤트가 발생할 때까지 진행됩니다.</p>
<h3 id="2-6-scan">2-6. scan</h3>
<p><strong>scan</strong>는 <span style = "color:blue"><strong>모든 데이터를 조합(변환)하며 각 결과 값을 출력한다.</strong></span> 실행할 때마다 입력값에 맞는 중간 결과 및 최종 결과를 구독자에게 발행한다. reduce연산자와 비슷하나 다른 점은 <strong>reduce</strong>는 마지막 1개 <strong>scan</strong>은 중간 중간 결과까지 계속 발행하는 것이다.</p>
<p><img src="https://images.velog.io/images/minnie_dev/post/db676626-aafb-4d77-a7ce-120ee82d150f/image.png" alt=""></p>
<pre><code class="language-java">String[] balls = new String[] {&quot;A&quot;, &quot;B&quot;, &quot;C&quot;};

Observable.fromArray(balls)
          .scan((ball1, ball2) -&gt; ball2 + &quot;(&quot; + ball1 + &quot;)&quot;)
          .subscribe(System.out::println);</code></pre>
<pre><code>위 코드가 출력된 내용
A
B(A)
C(B(A))</code></pre></br>
참고 : https://jaeryo2357.tistory.com/81



]]></description>
        </item>
        <item>
            <title><![CDATA[[RxJava] RxJava Observable의 정의, 형태, 생성]]></title>
            <link>https://velog.io/@minnie_dev/RxJava-Observable1</link>
            <guid>https://velog.io/@minnie_dev/RxJava-Observable1</guid>
            <pubDate>Thu, 17 Feb 2022 08:59:22 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>📝RxJava의 학습 순서</strong>
<span style ="color:blue"><strong>1. Observable 클래스를 명확하게 이해(특히 Hot Observable과 Cold Observable의 개념을 꼭 이해해야함)</strong></span>
2. 변환, 제어, 결합 연산자 등 카테고리별 주요 함수 공부3. 스케줄러의 의미, subscribeOn()과 observeOn()함수의 차이
4. 그 밖의 디버깅, 흐름 제어함수</p>
</blockquote>
<hr>
<h2 id="1-observable이란">1. Observable이란?</h2>
<blockquote>
<p><strong>Observable</strong>의 사전 뜻을 찾아보면 관찰할 수 있는, 식별 가능한 이러한 의미를 가지고 있다.
즉, <strong>Observable</strong>은 데이터의 흐름에 맞게 알림을 보내 observable을 구독하는 Observer가 데이터를 사용할 수 있도록 한다.
이러한 패턴을 <strong>Observer Pattern</strong>이라고 하며, Reactive Programming은 이 패턴에 기반을 둔다.</p>
</blockquote>
<p>➕ <strong>Disposable</strong>
<strong>Disposable</strong>이란 사전적 정의로는 사용 후 버리게 되어있는, 일회용의 라는 의미로 <strong>1회용 리소스</strong>를 나타낸다. </p>
<ul>
<li>void dispose() : 리소스를 삭제한다. </li>
<li>boolean isDisposed() : 이 리소스가 삭제된 경우 true를 리턴</li>
</ul>
<p>만약 Observable이 발행하는 아이템 개수가 정해져 있다면 모두 발행된 후 onComplete()가 호출되고 안전하게 종료될 것이다. 하지만 아이템을 무한 발행하거나 오래 실행되는 Observable의 경우, 제대로 종료하지 않으면 메모리 누수가 발생할 수 있다. 더 이상 Observable의 구독이 필요없을 때는 이를 폐기(dispose)하는 게 효율적이다. Disposable.dispose()를 호출해 언제든 아이템 발행을 중단할 수 있다.</p>
<h3 id="🧶-observable-동작-순서">🧶 Observable 동작 순서</h3>
<ol>
<li>Observable이 데이터 스트림을 처리하고, 완료되면 데이터를 발행(emit)한다.</li>
<li>데이터를 발행할 때마다 구독하고 있는 모든 Observer가 알림을 받는다.</li>
<li>Observer는 수신한 데이터를 가지고 어떠한 일을 한다.</li>
</ol>
<p><strong>Observable</strong>이 데이터를 발행하고 <strong>알림(Event)</strong>를 보내면 Observer는 Observable을 <strong>구독(Subscribe)</strong>해 데이터를 <strong>소비(Consume)</strong>한다.</p>
<h3 id="🧶-observable-event">🧶 Observable Event</h3>
<p><strong>Observable</strong>이 데이터를 발행 한 후 보내는 알림에는 3가지 Event를 사용하여 동작한다.
<strong>Emitter</strong>라는 인터페이스에 의해 아래 이벤트들이 선언된다.</p>
<ul>
<li><code>onNext</code> : 한 번에 하나씩 순차적으로 <strong>데이터를 발행</strong>한다.</li>
<li><code>onComplete</code> : 데이터 발행이 끝났음을 알리는 <strong>완료 이벤트</strong>를 Observer에 전달하여 onNext()를 더 호출하지 않음을 나타낸다.</li>
<li><code>onError</code> : <strong>오류</strong>가 발생했음을 Observer에 전달한다. onError 발생 이후에는 onNext와 onComplete가 발생하지 않는다</li>
</ul>
<p><span style = "color:red"><strong>데이터나 오류 내용을 발행 할 때 null은 발행할 수 없다.</strong> </span>
만약 아무런 데이터를 발행하지 않는 빈 Observable을 만들고 싶다면 <strong>Observable.empty()</strong> 연산자를 사용하면 된다.</p>
<h3 id="🧶-observable-subscribe">🧶 Observable Subscribe</h3>
<blockquote>
<p><strong>구독(Subscribe)</strong>란 단순하게 수신한 데이터를 가지고 할 행동을 정의하는 것이다. 
Observer는 <strong>subscribe()</strong>메소드에서 <span style ="color:blue"><strong>수신한 각각의 알림에 대해 실행할 내용을 지정</strong></span>한다.</p>
</blockquote>
<p>➕ <strong>Disposable</strong> class는 구독의 정상적인 <strong>해지</strong>를 돕는다.
onComplete 이벤트가 발생하면 dispose()를 호출해 Observable이 더 이상 데이터를 발행하지 않도록 구독을 해지한다. 또한 <strong>isDisposed()</strong>를 통해 구독이 해지되었는지 확인할 수 있다.</p>
<hr>
<h2 id="2-observable-종류">2. Observable 종류</h2>
<p>다양한 스트림을 처리하기에 Observable 하나만으로는 부족하다는 의견이 있어서 Rx2에서는 다양한 데이터 스트림에 따라 사용할 수 있는 Observable들이 늘어나게 되었다. 
<strong>Observable</strong> 스트림 외에도 <span style = "color:red"><strong>Single, Maybe, Completable,Flowable</strong></span> 처럼 특별한 스트림이 있다.</p>
<h3 id="2-1single">2-1.Single</h3>
<blockquote>
<p><strong>Single</strong>은 단 하나의 아이템만을 발행할 수 있다. create()를 사용하는 경우 Emitter를 사용해 데이터를 발행한다. 데이터를 한 번만 발행하기 때문에 onNext(), onComplete() 대신 onSuccess()를 사용해 데이터 발행이 완료됨을 알려준다. 오류 처리는 Observable의 Emitter와 동일하게 onError()를 이용해 구독자에게 알려준다. 주로 한 번의 데이터만 발행하는 API 통신, Client 요청에 대응하는 서버의 응답에 많이 사용한다. </p>
</blockquote>
<pre><code class="language-java">// Single.just()
Single.just(&quot;Single Test - Single.just()&quot;)
        .subscribe(System.out::println);

// Single.create()
Single&lt;String&gt; createdSingle = Single.create(new SingleOnSubscribe&lt;String&gt;() {
    @Override
    public void subscribe(@NonNull SingleEmitter&lt;String&gt; emitter) {
        emitter.onSuccess(&quot;Single Test - Single.create()&quot;);
    }
});
createdSingle.subscribe(System.out::println);</code></pre>
<pre><code>Single Test - Single.just()
    Single Test - Single.create()</code></pre><p>Observable을 Single로 변환할 수 있다. 단, 여러 데이터를 발행하는 Observable을 Single로 변환하면 에러가 발생할수 있다.</p>
<pre><code class="language-java">// Observable → Single 변환
// fromObservable() 사용
Observable&lt;String&gt; observable = Observable.just(&quot;Single Test : from.observable()&quot;);
Single.fromObservable(observable)
        .subscribe(System.out::println);

// Observable → Single 변환
// single() 사용
Observable.just(&quot;Single Test : just()&quot;)
        .single(&quot;Single Test : default Value&quot;) //default value
        .subscribe(System.out::println);</code></pre>
<h3 id="2-2maybe">2-2.Maybe</h3>
<blockquote>
<p><strong>Maybe</strong>는 Single과 비슷하게 최대 하나의 데이터를 가질 수 있지만, 아이템을 발행하거나 발행하지 않을 수도 있다는 점에서 차이가 있다. (Single은 1개 완료, Maybe는 0 or 1개 완료) 그래서 아이템을 발행했을 때에는 onSuccess()를 호출하고, 발행하지 않을 때에는 onComplete()를 호출한다. onSuccess()이후에 다시 onComplete()를 호출할 필요는 없다.</p>
</blockquote>
<pre><code class="language-java">Maybe.just(&quot;Hello World&quot;)
        .delay(10, TimeUnit.SECONDS, Schedulers.io())
        .subscribeWith(new DisposableMaybeObserver&lt;String&gt;() {
            @Override public void onSuccess(String value) {
                // 성공하여 값을 발생시켰음
                System.out.println(&quot;Maybe Test &gt;&gt;&gt; Success: &quot; + value);
            }
            @Override public void onError(Throwable error) {
                // 에러
                error.printStackTrace();
            }
            @Override public void onComplete() {
                // 성공했지만 값이 없을 때
                System.out.println(&quot;Maybe Test &gt;&gt;&gt; complete : Null&quot;);
            }
        });</code></pre>
<h3 id="2-3completable">2-3.Completable</h3>
<blockquote>
<p><strong>Completable</strong>은 데이터를 발행하는 Observable, Single, Maybe와 달리 아이템을 발행하지 않고, 정상적으로 실행이 종료되었는지에 대해 확인할 때 사용한다. 아이템 발행을 하지 않기 때문에 onNext(), onSuccess()는 쓰지 않고 onComplete()와 onError()만을 사용한다.
Room 데이터 베이스 사용에서와 같이 백그라운드에서 동작해야 되는 함수가 필요한 경우, 서버에 객체를 업데이트하는 요청을 보낸 경우 사용할 수 있다.</p>
</blockquote>
<pre><code class="language-java">// Completable
Completable.create(emitter -&gt;{
    emitter.onComplete();
}).subscribe(()-&gt;System.out.println(&quot;Completable Test &gt;&gt;&gt; completed 1&quot;));

Completable.fromRunnable(() -&gt;{
}).subscribe(() -&gt; System.out.println(&quot;Completable Test &gt;&gt;&gt; completed 2&quot;));
</code></pre>
<h3 id="2-4flowable">2-4.Flowable</h3>
<blockquote>
<p><strong>Flowable</strong>은 연속적으로 데이터가 흐르는 스트림을 생성한다. 데이터가 흐름에 따라 화면을 갱신시키는 코드에 사용될 수 있다. 통신, DB, 대량의 데이터 처리에 Flowable을 사용하는 것을권장한다. 0개~N개의 데이터를 발행할 수 있다는 점에서 Observable과 유사하지만, Observable은 Backpressure이 없는 반면에 <strong>Flowable은 Backpressure가 존재</strong>한다.</p>
</blockquote>
<p>➕ <strong>BackPressure</strong>란 배압이라는 뜻으로 <span style ="color:blue"><strong>데이터 생산과 소비가 불균형적일 때 일어나는 현상</strong></span>이다. 
만약 10,000개의 데이터를 0.1초마다 발행하고, 소비는 10초마다 한다면 소비와 관계없이 데이터는 스트림에 계속 쌓이게 된다.</p>
<p>Observable이 데이터를 발행하는 속도를 Observable의 소비 속도가 따라가지 못하는 것이다. 
이는 결국 메모리가 overflow되고 OutOfMemoryError로 이어져 앱이 터질 것이다. 
이러한 현상을 배압(Backpressure)이라고 하며  RxJava에서는 배압 현상을 제어할 수 있는 방법을 제공한다.</p>
<p>🎈 <strong>Observable을 사용해야하는 경우</strong></p>
<ul>
<li>1,000개 미만의 데이터 흐름이 발생하는 경우</li>
<li>적은 데이터 소스만을 활용하여 OutOfMemoryException이 발생할 확률이 적은 경우</li>
<li>마우스 이벤트나 터치 이벤트와 같은 GUI 프로그래밍을 하는 경우 (초당 1,000회 이하의 이벤트는 Observable의 sample()이나 debounce()로 핸들링 가능)</li>
<li>동기적인 프로그래밍이 필요하지만 플랫폼에서 Java Streams을 지원하지 않는 경우</li>
</ul>
<p>🎈<strong>Flowable을 사용해야하는 경우</strong></p>
<ul>
<li>10,000개 이상의 데이터 흐름이 발생하는 경우</li>
<li>디스크에서 파일을 읽는 경우 (기본적으로 Blocking/Pull-based 방식)</li>
<li>JDBC에서 데이터베이스를 읽는 경우 (기본적으로 Blocking/Pull-based 방식)</li>
<li>네트워크 IO 실행 시</li>
<li>Blocking/Pull-based 방식을 사용하고 있는데 나중에 Non-Blocking 방식의 Reactive API/드라이버에서 데이터를 가져올 일이 있는 경우</li>
</ul>
<hr>
<h2 id="3-observable-생성-연산자">3. Observable 생성 연산자</h2>
<p>RxJava에서는 연산자(Operator)를 통해 기존 데이터를 참조, 변형하여 Observable을 생성할 수 있다. </p>
<h3 id="3-1create">3-1.create()</h3>
<p><strong>create</strong>를 사용하면 Emitter를 이용하여 직접 아이템을 발행하고, 아이템 발행의 완료나 오류(Complete/Error)의 알림을 직접 설정할 수 있다.
해당 Observable을 구독하기 위해서 <strong>subscribe()</strong>를 호출해서 Observer나 Consumer를 추가해준다. 그리고 아이템의 발행이 끝났다면 반드시 <strong>onComplete()</strong>를 호출해야 한다.
onComplete가 호출된 후라면 아이템이 더 발행되더라도 구독자는 데이터를 받지 못한다.</p>
<p><img src="https://images.velog.io/images/minnie_dev/post/e0aefd1e-5a92-4cef-809e-5a0d47f6cbae/image.png" alt=""></p>
<pre><code class="language-java">private void createObservable(){
    source = Observable.create(emitter -&gt; {
         emitter.onNext(&quot;Hello&quot;);
         emitter.onNext(&quot;Hi&quot;);
         emitter.onComplete();
    });
    source.subscribe(System.out::println);
}</code></pre>
<p><img src="https://images.velog.io/images/minnie_dev/post/1b221cb7-5ec0-48db-8c19-29a944c0cdcc/image.png" alt=""></p>
<p>실제로 create()연산자는 개발자가 직접 Emitter를 제어하기 때문에 주의해서 사용해야 한다.
예를 들어 Observable을 더 이상 사용하지 않을 때에는 등록된 Callback을 모두 해제하지 않으면 메모리 릭이 발생하고, BackPressure를 직접처리해야한다.</p>
<h3 id="3-2-just">3-2. just()</h3>
<p><strong>just()</strong>는 해당 아이템을 그대로 발행하는 Observable을 생성해준다. just()연산자의 인자로 넣은 아이템을 차례로 발행하며, 한 개의 아이템을 넣을 수도 있고, 타입이 같은 여러 아이템을 넣을 수도 있다.</p>
<p><img src="https://images.velog.io/images/minnie_dev/post/c4e5db42-bf92-4338-958d-eab5ae8ccfc4/image.png" alt=""></p>
<pre><code class="language-java">private void justObservable(){
    Observable&lt;String&gt; source = Observable.just(&quot;Hello&quot;,&quot;Hi&quot;);
    source.subscribe(System.out::println);
}</code></pre>
<p><img src="https://images.velog.io/images/minnie_dev/post/421af373-75c1-4bf4-afe3-3a76b772d3dd/image.png" alt=""></p>
<h3 id="3-3-fromxxx">3-3. fromXXX()</h3>
<p>여러 데이터를 다뤄야 하는 경우 사용한다. 정의된 메소드의 종류는 다음과 같으며 특정 타입의 데이터를 Observable로 바꿔주는 메소드이다. Array, Iterable, Single, Maybe...등 다양한 특정 타입의 데이터로 바꿀 수 있다.
<img src="https://images.velog.io/images/minnie_dev/post/bcdc0265-4382-4f0e-afae-de7c07613deb/image.png" alt=""></p>
<h3 id="3-4-그-외">3-4. 그 외</h3>
<p>이 외에도 다른 생성 연산자들이 많다.</p>
<table>
<thead>
<tr>
<th>Method</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>Interval</td>
<td>시간 간격을 두고 데이터를 발행하는 Observable 생성</td>
</tr>
<tr>
<td>Range</td>
<td>특정 범위 내 Integer 형태의 아이템을 발행하는 Observable 생성</td>
</tr>
<tr>
<td>Repeat</td>
<td>아이템을 지정한 횟수만큼, 혹은 무한히 반복하여 발행</td>
</tr>
<tr>
<td>Start</td>
<td>연산 후 특정 값을 반환, 함수처럼 작용함</td>
</tr>
<tr>
<td>Timer</td>
<td>지정한 시간 delay 이후 아이템 발행</td>
</tr>
</tbody></table>
</br>

<p>참고 :<a href="https://blog.yena.io/studynote/2020/10/23/Android-RxJava(2).html">https://blog.yena.io/studynote/2020/10/23/Android-RxJava(2).html</a>
<a href="https://4z7l.github.io/2020/12/23/rxjava-7.html">https://4z7l.github.io/2020/12/23/rxjava-7.html</a>
<a href="https://velog.io/@ryalya/Android-RxJava-4-Single-Maybe-Completable">https://velog.io/@ryalya/Android-RxJava-4-Single-Maybe-Completable</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[RxJava] RxJava Cold vs Hot Observable ]]></title>
            <link>https://velog.io/@minnie_dev/RxJava-Observable2</link>
            <guid>https://velog.io/@minnie_dev/RxJava-Observable2</guid>
            <pubDate>Thu, 17 Feb 2022 03:30:24 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>📝RxJava의 학습 순서</strong>
<span style ="color:blue"><strong>1. Observable 클래스를 명확하게 이해(특히 Hot Observable과 Cold Observable의 개념을 꼭 이해해야함)</strong></span>
2. 변환, 제어, 결합 연산자 등 카테고리별 주요 함수 공부
3. 스케줄러의 의미, subscribeOn()과 observeOn()함수의 차이
4. 그 밖의 디버깅, 흐름 제어함수</p>
</blockquote>
<hr>
<h2 id="cold-observable-vs-hot-observable">Cold Observable vs Hot Observable</h2>
<blockquote>
<p><strong>Observable</strong>의 사전 뜻을 찾아보면 관찰할 수 있는, 식별 가능한 이러한 의미를 가지고 있다.
즉, observable은 데이터의 흐름에 맞게 알림을 보내 Observer가 데이터를 사용할 수 있도록 한다. </p>
</blockquote>
<p>Observable에는 <strong>Cold Observable</strong>과 <strong>Hot Observable</strong>이 있다.
일반적으로 우리가 사용하는 것들은 모두 Cold Observable이라고 한다.</p>
<p>⭐자세하게 보기전에 간단하게 유튜브로 Hot Observable과 Cold Observable의 차이를 설명하자면
<span style="color:red"><strong>Cold Observable</strong></span>은 Youtube 동영상을 재생하는 것 처럼, 구독한 순간부터 아이템을 발행시키는 것이고
<span style="color:red"><strong>Hot Observable</strong></span>은 Live 방송을 시청하는 것처럼, 구독하기 전 후 상관 없이 아이템 발행이 시작된 이후로 모든 구독자들에게 동시에 같은 아이템을 발행시키는 것이다.⭐</p>
<h3 id="🌈-cold-observable">🌈 Cold Observable</h3>
<blockquote>
<p><strong>Cold Observable</strong>은 Observer가 구독하는 동안 이벤트 방출을 한다. Observable을 생성하고 Observer가 subscribe를 호출할 때 까지 데이터 발행을 하지 않는다. 이러한 방식을 lazy하다고 한다. 원하는 시점에 데이터를 요청하고 처음부터 끝까지 결과를 받아온다.
<strong>Cold Observable의 종류</strong> : Observable, Flowable, Single, Maybe, Completable
<strong>Cold Observable 예</strong> : 데이터베이스 쿼리, 파일읽기, API 요청 등</p>
</blockquote>
<p>아래 코드는 interval연산자를 통해 1초마다 아이템을 발행하는 Cold Observable의 예시이다.</p>
<pre><code class="language-java">    private void coldObservable(){
        Observable&lt;Long&gt; src = Observable.interval(1, TimeUnit.SECONDS);
        src.subscribe(value-&gt; System.out.println(&quot;First: &quot; + value));
        try {
            Thread.sleep(2500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        src.subscribe(value-&gt; System.out.println(&quot;Second: &quot; + value));
        try {
            Thread.sleep(2500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }</code></pre>
<p>첫번째로 Observable을 구독하고, 2.5초후 후에 새로운 구독자로 다시 구독한 모습니다. 
두 Observer 모두 처음부터 발행한 결과를 확인해볼 수 있다. </p>
<p><img src="https://images.velog.io/images/minnie_dev/post/b50b7d4c-8400-4efa-9988-6997603cc432/image.png" alt=""></p>
<h3 id="🌈-hot-observable">🌈 Hot Observable</h3>
<blockquote>
<p><strong>Hot Observable</strong>은 구독 여부와 관계 없이 이벤트 방출한다.첫번째 구독자가 Observable을 구독한 몇 초 후에 두번째 구독자가 같은 Observable을 구독한다면, 둘은 동시에 같은 아이템을 수신하며 두번째 구독자는 구독 이전에 발행된 아이템을 놓칠 수도 있다.
<strong>Hot Observable의 종류</strong> : subject, procssor
<strong>Hot Observable 예</strong> : 안드로이드 UI 이벤트(버튼 클릭, 키보드 입력 등..)</p>
</blockquote>
<p>Hot Observable을 구현하기 위한 방법에는 여러 가지가 있다.
그 중 하나로 <strong>ConnectableObservable</strong>이라는 특수한 유형의 Observable이 있다. <strong>ConnectableObservable</strong>은 구독을 요청해도 데이터를 곧바로 발행하지 않는다는 점이다.</p>
<p>우선 <strong>publish()</strong>연산자를 통해 일반적인 Observable을 Hot Observable로 변환한다. <strong>publish()</strong>만으로 아이템 replay가 활성화되지 않는다. <strong>connect()</strong> 연산자를 호출할 때에 비로소 아이템을 발행하기 시작한다.</p>
<pre><code class="language-java">ConnectableObservable&lt;Long&gt; src = Observable.interval(1,TimeUnit.SECONDS).publish();
src.connect();
src.subscribe(value-&gt;System.out.println(&quot;First: &quot; +value));
try {
    Thread.sleep(2500);
} catch (InterruptedException e) {
    e.printStackTrace();
}
src.subscribe(value-&gt; System.out.println(&quot;Second: &quot; + value));
try {
    Thread.sleep(2500);
} catch (InterruptedException e) {
    e.printStackTrace();
}</code></pre>
<p>첫번째 구독자는 2.5초 동안 0~2 아이템을 발행하고, 두번째 구독자는 2.5초 뒤 2부터 수신하는 것을 볼 수 있다.</p>
<p><img src="https://images.velog.io/images/minnie_dev/post/f5f2473e-f1db-47e6-8aee-b14a99c50ffc/image.png" alt=""></p>
<p>connect()말고 <strong>autoConnect()</strong>라는 연산자도 존재한다.
<strong>autoConnect</strong>연산자는 connect()를 호출하지 않더라도 구독 즉시 아이템을 발행할 수 있도록 도와주는 연산자이다. </p>
<pre><code class="language-java">private void hotObservableAutoConnect(){
    System.out.println(&quot;AutoConnect Start&quot;);
    Observable&lt;Long&gt; src =
        Observable.interval(1,TimeUnit.SECONDS)
                .publish()
                .autoConnect(2);

    src.subscribe(value-&gt;System.out.println(&quot;First: &quot; +value));
    try {
        Thread.sleep(2500);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    src.subscribe(value-&gt; System.out.println(&quot;Second: &quot; + value));
    try {
        Thread.sleep(2500);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}</code></pre>
<p><strong>autoConnect</strong> 매개변수는 아이템을 발행하는 구독자의 수이다. autoConnect(2)라고 설정하고 첫번째 구독자는 바로 구독하고 두번째 구독자는 2.5초 뒤에 구독하는 코드이다.
구독자의 수인 2까지 채워지고 나서야 아이템이 발행되는 것을 확인할 수 있다. 0이하의 수를 입력하면 구독자 수와 관계 없이 곧바로 아이템 발행을 시작한다.</p>
<p><img src="https://images.velog.io/images/minnie_dev/post/b2959441-c74c-4633-9f06-8ee697d076fa/image.png" alt=""></p>
<p>참고 : <a href="https://4z7l.github.io/2020/12/08/rxjava-4.html">https://4z7l.github.io/2020/12/08/rxjava-4.html</a>
<a href="https://blog.yena.io/studynote/2020/11/08/Android-RxJava(3).html">https://blog.yena.io/studynote/2020/11/08/Android-RxJava(3).html</a></p>
]]></description>
        </item>
    </channel>
</rss>