<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>안드로이드 스튜디오 앱 개발 시행착오 저장블로그</title>
    <link>https://appdevelopjava.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Tue, 14 Jul 2026 02:49:27 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>songyooho</managingEditor>
    <item>
      <title>팀프로젝트</title>
      <link>https://appdevelopjava.tistory.com/98</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;gt;팀프로젝트&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://appdevelopjava.tistory.com/97&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://appdevelopjava.tistory.com/97&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1724416920366&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;커스텀 뷰 작성&quot; data-og-description=&quot;package com.example.alarmmemo.presentation.memoimport android.content.Contextimport android.graphics.Bitmapimport android.graphics.BitmapFactoryimport android.graphics.Canvasimport android.graphics.DashPathEffectimport android.graphics.Paintimport android.&quot; data-og-host=&quot;appdevelopjava.tistory.com&quot; data-og-source-url=&quot;https://appdevelopjava.tistory.com/97&quot; data-og-url=&quot;https://appdevelopjava.tistory.com/97&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cPXzus/hyWShxgO6q/eeodukDUrdBotULzIoc5kK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/7n5b7/hyWSax7EPr/XxbgCLOQBAy2Tk38sF9oq1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bieBTG/hyWSlzFLeK/PVQX39FTHzDOaW4ElZnui0/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400&quot;&gt;&lt;a href=&quot;https://appdevelopjava.tistory.com/97&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://appdevelopjava.tistory.com/97&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cPXzus/hyWShxgO6q/eeodukDUrdBotULzIoc5kK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/7n5b7/hyWSax7EPr/XxbgCLOQBAy2Tk38sF9oq1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bieBTG/hyWSlzFLeK/PVQX39FTHzDOaW4ElZnui0/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;커스텀 뷰 작성&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;package com.example.alarmmemo.presentation.memoimport android.content.Contextimport android.graphics.Bitmapimport android.graphics.BitmapFactoryimport android.graphics.Canvasimport android.graphics.DashPathEffectimport android.graphics.Paintimport android.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;appdevelopjava.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>코틀린-안드로이드</category>
      <author>songyooho</author>
      <guid isPermaLink="true">https://appdevelopjava.tistory.com/98</guid>
      <comments>https://appdevelopjava.tistory.com/98#entry98comment</comments>
      <pubDate>Fri, 23 Aug 2024 21:42:03 +0900</pubDate>
    </item>
    <item>
      <title>커스텀 뷰 작성</title>
      <link>https://appdevelopjava.tistory.com/97</link>
      <description>&lt;div style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;package com.example.alarmmemo.presentation.memo

import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Canvas
import android.graphics.DashPathEffect
import android.graphics.Paint
import android.graphics.Path
import android.graphics.Rect
import android.graphics.RectF
import android.net.Uri
import android.util.AttributeSet
import android.util.Log
import android.util.TypedValue
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
import android.widget.FrameLayout
import android.widget.LinearLayout
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet.Constraint
import androidx.core.view.drawToBitmap
import com.example.alarmmemo.R
import com.example.alarmmemo.data.model.MemoModel
import com.example.alarmmemo.databinding.MemoBitmapMenuBinding

class MemoView(private val context: Context, attrs: AttributeSet): FrameLayout(context,attrs) {
    //세팅 프로퍼티
    private var eraseOn =false
    private val history = ArrayDeque&amp;lt;HistoryItem&amp;gt;()

    private var curX = 0f
    private var curY = 0f
    private var curIdx:Int? =null
    private var activatedIdx:Int?=null
    private var drawList = ArrayList&amp;lt;Path&amp;gt;()
    private var drawPaintList = ArrayList&amp;lt;Paint&amp;gt;()
    private var drawActivate = true
    private var curCase:Int = 0
    private val minHeight = dpToPx(context,50f)
    private val minWidth = dpToPx(context,50f)
    private var borderSize = 50f
    private val bitmaps = ArrayList&amp;lt;Bitmap&amp;gt;()
    private val bitmapRectFs = ArrayList&amp;lt;RectF&amp;gt;()
    private val bitmapPaint = Paint()
    private val borderPaint = Paint().apply {
        color = context.getColor(R.color.black)
        style = Paint.Style.STROKE
        strokeWidth = dpToPx(context,2f)
        pathEffect = DashPathEffect(floatArrayOf(10f,10f),0f)
    }
    private var drawPaint = Paint().apply {
        color = context.getColor(R.color.black)
        style = Paint.Style.STROKE
        strokeWidth = dpToPx(context,4f)
    }

    private val bitmapMenu by lazy {
        val inflater = LayoutInflater.from(context)
        MemoBitmapMenuBinding.inflate(inflater,this@MemoView,false).apply {
            bitmapMenuDelete.setOnClickListener {
                removeBitmap()
            }
            bitmapMenuCopy.setOnClickListener{
                addBitmap()
            }
            root.visibility =View.GONE
        }
    }

    init {
        addView(bitmapMenu.root)
    }

    fun addBitmap(uri: Uri){
        getBitmapFromUri(context,uri).getOrNull()?.let {
            bitmaps+=it
            bitmapRectFs+=RectF(0f,0f, dpToPx(context,100f),it.height.toFloat()*dpToPx(context,100f)/it.width.toFloat())
            history.addLast(HistoryItem(ActionType.AddBitmap,bitmaps.size-1,Pair(bitmaps.last(),bitmapRectFs.last())))
        }
        invalidate()
    }

    private fun addBitmap(){
        activatedIdx?.let {
            bitmaps+=Bitmap.createBitmap(bitmaps[it])
            bitmapRectFs+=RectF(bitmapRectFs[it])
            history.addLast(HistoryItem(ActionType.AddBitmap,bitmaps.size-1,Pair(bitmaps.last(),bitmapRectFs.last())))
        }
        invalidate()
    }

    fun addDraw(x:Float?,y:Float?){
        if(x==null||y==null) return
        drawList+=Path().apply { moveTo(x,y) }
        drawPaintList+=Paint().apply {
            color = context.getColor(R.color.black)
            style = Paint.Style.STROKE
            strokeWidth = dpToPx(context,2f)
        }
        history.addLast(HistoryItem(ActionType.AddDraw, drawList.size-1,Pair(drawList.last(),drawPaintList.last())))
    }

    fun removeBitmap(){
        activatedIdx?.let {
            history.addLast(HistoryItem(ActionType.EraseBitmap,it,Pair(bitmaps[it],bitmapRectFs[it])))
            bitmaps.removeAt(it)
            bitmapRectFs.removeAt(it)
            activatedIdx=null
            removeView(bitmapMenu.root)
        }
        invalidate()
    }

    //1~9로 9구획으로 나눔(왼위,위,오른위,왼,내부,오른,왼아래,아래,오른아래)
    fun checkCase(x:Float,y:Float):Pair&amp;lt;Int,Int&amp;gt;?{
        bitmapRectFs.forEachIndexed { idx,rect -&amp;gt;
            val hori = if(x in rect.left ..rect.left+borderSize){
                1
            }else if(x in rect.left+borderSize..rect.right-borderSize){
                2
            }else if(x in rect.right-borderSize..rect.right){
                3
            }else{
                4
            }
            val vert = if(y in rect.top..rect.top+borderSize){
                1
            }else if(y in rect.top+borderSize..rect.bottom-borderSize){
                2
            }else if(y in rect.bottom - borderSize..rect.bottom){
                3
            }else{
                4
            }

            val result = if(vert==1){
                when(hori){
                    1 -&amp;gt; 1
                    2 -&amp;gt; 2
                    3 -&amp;gt; 3
                    else -&amp;gt; 10
                }
            }else if(vert==2){
                when(hori){
                    1-&amp;gt;4
                    2-&amp;gt;5
                    3-&amp;gt;6
                    else-&amp;gt;10
                }
            }else if(vert==3){
                when(hori){
                    1-&amp;gt;7
                    2-&amp;gt;8
                    3-&amp;gt;9
                    else-&amp;gt;10
                }
            }else{
                10
            }
            if(result!=10){
                return Pair(idx,result)
            }
        }
        return null
    }

    private fun resize(offX:Float,offY:Float,x:Float?=null,y:Float?=null){
        curIdx?.let {
            Log.d(&quot;메모&quot;,curCase.toString())
            if(curCase==1||curCase==2||curCase==3){
                val new = bitmapRectFs[it].top + offY
                val newHeight = bitmapRectFs[it].height() - offY
                if(newHeight in minHeight..height.toFloat()){
                    bitmapRectFs[it].top = new
                }
            }
            if(curCase==7||curCase==8||curCase==9){
                val new = bitmapRectFs[it].bottom + offY
                val newHeight = bitmapRectFs[it].height() +offY
                if(newHeight in minHeight..height.toFloat()){
                    bitmapRectFs[it].bottom = new
                }
            }
            if(curCase==1||curCase==4||curCase==7){
                val new = bitmapRectFs[it].left + offX
                val newWidth = bitmapRectFs[it].width() - offX
                if(newWidth in minWidth..width.toFloat()){
                    bitmapRectFs[it].left = new
                }
            }
            if(curCase==3||curCase==6||curCase==9){
                val new = bitmapRectFs[it].right + offX
                val newWidth = bitmapRectFs[it].width() + offX
                if(newWidth in minWidth..width.toFloat()){
                    bitmapRectFs[it].right = new
                }
            }
        }
        invalidate()
        if(x!=null&amp;amp;&amp;amp;y!=null){
            curX=x
            curY=y
        }

    }

    private fun setActivate(x:Float?,y:Float?){
        if(x==null||y==null) return
        if(activatedIdx!=null&amp;amp;&amp;amp;!bitmapRectFs[activatedIdx!!].contains(x,y)){
            activatedIdx=null
        }
        bitmapRectFs.forEachIndexed { idx, rect -&amp;gt;
            if(rect.contains(x, y)){
                activatedIdx=idx
                return
            }
        }
        activatedIdx = null
    }

    override fun dispatchDraw(canvas: Canvas) {
        val list = ArrayList&amp;lt;Pair&amp;lt;MemoType,Any?&amp;gt;&amp;gt;()
        history.forEach {
            when(it.action){
                ActionType.EraseTextBox -&amp;gt; list.remove(Pair(MemoType.TextBox,it.data))
                ActionType.EraseDraw -&amp;gt; list.remove(Pair(MemoType.Draw,it.data))
                ActionType.EraseBitmap -&amp;gt; list.remove(Pair(MemoType.Bitmap,it.data))
                ActionType.EraseText -&amp;gt; list.remove(Pair(MemoType.Text,it.data))
                ActionType.AddTextBox -&amp;gt; list+=Pair(MemoType.TextBox,it.data)
                ActionType.AddDraw -&amp;gt; list+=Pair(MemoType.Draw,it.data)
                ActionType.AddBitmap -&amp;gt; list+=Pair(MemoType.Bitmap,it.data)
                ActionType.AddText -&amp;gt; list+=Pair(MemoType.Text,it.data)
            }
        }
        list.forEach {
            val (type,data) = it
            when(type){
                MemoType.TextBox -&amp;gt; null
                MemoType.Draw -&amp;gt; {
                    val (path,paint) = data as Pair&amp;lt;Path,Paint&amp;gt;
                    canvas.drawPath(path,paint)
                }
                MemoType.Bitmap -&amp;gt; {
                    val (bitmap,rectf) = data as Pair&amp;lt;Bitmap,RectF&amp;gt;
                    canvas.drawBitmap(bitmap,null,rectf,bitmapPaint)
                }
                MemoType.Text -&amp;gt; null
                MemoType.Default -&amp;gt; null
            }
        }
        Log.d(&quot;메모&quot;,list.toString())

        activatedIdx?.let {
            canvas.drawBitmap(bitmaps[it],null,bitmapRectFs[it],bitmapPaint)
            canvas.drawRect(bitmapRectFs[it],borderPaint)
        }
        super.dispatchDraw(canvas)
    }


    override fun onTouchEvent(event: MotionEvent?): Boolean {
        event?.let {
            when(it.action){
                MotionEvent.ACTION_DOWN -&amp;gt; {

                    bitmapMenu.root.visibility = View.GONE

                    curX = it.x
                    curY = it.y
                    setActivate(curX,curY)
                    if(activatedIdx!=null){
                        val selected = checkCase(curX,curY)
                        if(selected == null){
                            curIdx = null
                        }else{
                            curIdx = selected.first
                            curCase = selected.second
                        }
                    }

                    invalidate()

                    if(activatedIdx==null&amp;amp;&amp;amp;drawActivate){
                        addDraw(curX,curY)
                        return true
                    }

                    if(curIdx!=null) return true
                }
                MotionEvent.ACTION_MOVE -&amp;gt;{
                    Log.d(&quot;메모&quot;,drawActivate.toString()+&quot;,&quot;+activatedIdx.toString())
                    if(activatedIdx==null&amp;amp;&amp;amp;drawActivate){
                        drawList.last().lineTo(it.x,it.y)
                        invalidate()
                    }
                    val offX=it.x - curX
                    val offY=it.y - curY
                    resize(offX,offY,it.x,it.y)
                    if(curCase==5){
                        curIdx?.let { idx-&amp;gt;
                            val maxX = minOf(bitmapRectFs[idx].right+offX,width.toFloat())
                            val minX = maxOf(bitmapRectFs[idx].left+offX,0f)
                            val maxY = minOf(bitmapRectFs[idx].bottom+offY,height.toFloat())
                            val minY = maxOf(bitmapRectFs[idx].top+offY,0f)

                            val adjLeft = if(offX&amp;gt;0) maxX - bitmapRectFs[idx].width() else minX
                            val adjTop = if(offY&amp;gt;0) maxY - bitmapRectFs[idx].height() else minY

                            bitmapRectFs[idx].offsetTo(adjLeft,adjTop)
                            invalidate()
                            curX = it.x
                            curY = it.y
                        }
                        invalidate()
                    }
                    return true
                }
                MotionEvent.ACTION_UP -&amp;gt;{
                    if(activatedIdx!=null){
                        bitmapMenu.root.x=(bitmapRectFs[activatedIdx!!].left+bitmapRectFs[activatedIdx!!].right)/2f - bitmapMenu.root.width/2f
                        bitmapMenu.root.y=bitmapRectFs[activatedIdx!!].top - dpToPx(context,50f)
                        bitmapMenu.root.visibility = View.VISIBLE
                        bitmapMenu.root.bringToFront()
                    }
                    curIdx = null
                    invalidate()
                    return true
                }
            }
        }


        return super.onTouchEvent(event)
    }
}

fun dpToPx(context: Context, dp:Float): Float{
    return TypedValue.applyDimension(
        TypedValue.COMPLEX_UNIT_DIP,
        dp,
        context.resources.displayMetrics
    )
}

fun getBitmapFromUri(context: Context, uri: Uri):Result&amp;lt;Bitmap&amp;gt;{
    return runCatching {
        val inputStream = context.contentResolver.openInputStream(uri)
        val bitmap = BitmapFactory.decodeStream(inputStream)
        inputStream?.close()
        bitmap
    }
}

enum class MemoType{
    TextBox,
    Draw,
    Bitmap,
    Text,
    Default
}

enum class ActionType{
    EraseTextBox,
    EraseDraw,
    EraseBitmap,
    EraseText,
    AddTextBox,
    AddDraw,
    AddBitmap,
    AddText

}

data class HistoryItem(
    val action:ActionType,
    val zidx: Int,
    val data:Any?
)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-일차적으로 그림을 그리고 이미지를 추가,삭제, 복사,크기조절하는 기능을 넣었고 뒤로가기, 돌아가기 기능을 위한 history부분을 구현하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-확장성을 고려하며 추가기능을 넣기위한 기반을 짜두었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@dispatchDraw에서 그려지는 부분&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-뷰와 자식뷰(addView로 추가된 뷰도 포함)는&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;super.dispatchDraw(canvas)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이부분에 의해 그려진다. 그렇기 때문에 자식뷰를 그려지는 부분보다 위에 놓고 싶으면 이 코드를 아래로 밀어두면 된다.&lt;/p&gt;</description>
      <category>코틀린 팀플 최종 - 알람 메모앱 만들기</category>
      <author>songyooho</author>
      <guid isPermaLink="true">https://appdevelopjava.tistory.com/97</guid>
      <comments>https://appdevelopjava.tistory.com/97#entry97comment</comments>
      <pubDate>Fri, 23 Aug 2024 21:41:31 +0900</pubDate>
    </item>
    <item>
      <title>70일차)특강(flow, Uistate), 팀프로젝트(YoutubeProject)</title>
      <link>https://appdevelopjava.tistory.com/96</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;gt;특강&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Flow사용이유&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-livedata의 경우 안드로이드 플랫폼에 종속적이고 UI가 없는곳에서 사용하기 어려움&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-domainLayer에서 livedata사용이 어려움&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;=&amp;gt;이로 인해 Flow를 상ㅇ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Flow란&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-코틀린에서 쓸 수 있는 비동기 스트림(데이터를 일정한 순서에 따라 연속적으로 처리,전달하는 방식)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-사용방법&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;1&amp;gt;flow builder함수로 코드블록 구성후 emit을 호출해 데이터를 보냄&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;=&amp;gt;flow는 콜드 스트림이라 요청측에서 collect호출해서 값ㅇㄹ 발생시킴&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;2&amp;gt;생명주기를 모르므로 생명주기에 따른 처리 필요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;3&amp;gt;cold stream이므로 연속해서 들어오는 데이터 처리 불가.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;4&amp;gt;예시&lt;/p&gt;
&lt;pre id=&quot;code_1724064085324&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;override suspend fun searchUserVideoList(query: String): Flow&amp;lt;List&amp;lt;User&amp;gt;&amp;gt; {
    return flow { emit(toVideoUser(searchRemoteDataSource.getSearchVideo(query).documents.orEmpty()))  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;div style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;searchRepository.searchUserImageList(query)
    .zip(searchRepository.searchUserVideoList(query)) { imageList, videoList -&amp;gt;
        return@zip (imageList + videoList).sortedByDescending { it.dateTime }
    }.flowOn(Dispatchers.IO)
    .catch { error -&amp;gt;
        _searchUiState.value = UiState.Error(error.toString())
    }
    .collect {
        _searchUiState.value = UiState.Success(it)
    }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. StateFlow&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-초기값이 필요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-replaycache를 1로 고정된 값을 가짐(livedata와 유사)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-non-suspending으로 .value로 get, set이 가능 &amp;lt;=&amp;gt;livedata는 앱이 백그라운드로 가면 statflow와 달리 옵저버가 관찰 취소&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-state관리에 최적화 되어있으며 항상 마지막 상태를 return&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. sharedFlow&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-초기값이 필요없음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-replayCache를 직접 정의 가능하며 기본값은 0.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;=&amp;gt;기본값사용시 구성 변경해도 기존 구독자뿐만 아니라 새구독자에게도 값이 방출 x&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-주의점: viewmodel에서 init에서 sharedflow로 데이터 전송시 collector가 아직 수집 시작을 안 한 경우 해당 데이터 방출을 놓칠 수 있음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5.UiState&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-데이터 레이어에서 가져온 데이터, 도중의 로딩상태, 기본상태, 성공, 실패등이 경우에 따른 Ui state로 사용자에게 보여질 UI상태를 나타낸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-주로 sealed class(interface)로 많이 사용함.&lt;/p&gt;
&lt;pre id=&quot;code_1724064334656&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sealed interface UiState&amp;lt;out T&amp;gt; {

    data class Success&amp;lt;T&amp;gt;(val data: T) : UiState&amp;lt;T&amp;gt;

    data class Error(val message: String) : UiState&amp;lt;Nothing&amp;gt;

    object Loading : UiState&amp;lt;Nothing&amp;gt;

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Mapper&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DataSource에서 가져온 entity를 프로그램내에서 사용할 model로 변환 시키는 메소드를 mapper라 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;gt;팀프로젝트&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://appdevelopjava.tistory.com/95&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://appdevelopjava.tistory.com/95&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1724072458046&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;데이터 레이어 작성, 로그인 및 회원가입과 스플래시 작성&quot; data-og-description=&quot;&amp;gt;데이터 레이어 구조@데이터 종류1. 캐시 데이터1)카테고리 데이터 - 채널, 비디오-CategoryModel을 nextPageToken과 CategoryResponse로 나누어 저장.=&amp;gt;새 데이터가 들어오면 토큰은 replace하고 response는 추가하&quot; data-og-host=&quot;appdevelopjava.tistory.com&quot; data-og-source-url=&quot;https://appdevelopjava.tistory.com/95&quot; data-og-url=&quot;https://appdevelopjava.tistory.com/95&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/kPFS0/hyWOgZ0Dgj/lfMlekCcKQsPp9NV4ehFs1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/dL73Dr/hyWOkH8UAx/X3tNTwUnjkTXCT1uTvH9C0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/cGgRA1/hyWOccg13h/WA2YowITJkKNAkBXgzKCW1/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400&quot;&gt;&lt;a href=&quot;https://appdevelopjava.tistory.com/95&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://appdevelopjava.tistory.com/95&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/kPFS0/hyWOgZ0Dgj/lfMlekCcKQsPp9NV4ehFs1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/dL73Dr/hyWOkH8UAx/X3tNTwUnjkTXCT1uTvH9C0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/cGgRA1/hyWOccg13h/WA2YowITJkKNAkBXgzKCW1/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;데이터 레이어 작성, 로그인 및 회원가입과 스플래시 작성&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;gt;데이터 레이어 구조@데이터 종류1. 캐시 데이터1)카테고리 데이터 - 채널, 비디오-CategoryModel을 nextPageToken과 CategoryResponse로 나누어 저장.=&amp;gt;새 데이터가 들어오면 토큰은 replace하고 response는 추가하&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;appdevelopjava.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>코틀린-안드로이드</category>
      <author>songyooho</author>
      <guid isPermaLink="true">https://appdevelopjava.tistory.com/96</guid>
      <comments>https://appdevelopjava.tistory.com/96#entry96comment</comments>
      <pubDate>Mon, 19 Aug 2024 22:01:03 +0900</pubDate>
    </item>
    <item>
      <title>데이터 레이어 작성, 로그인 및 회원가입과 스플래시 작성</title>
      <link>https://appdevelopjava.tistory.com/95</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;gt;데이터 레이어 구조&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #1d1c1d; text-align: left;&quot;&gt;@데이터 종류&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #1d1c1d; text-align: left;&quot;&gt;1. 캐시 데이터&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #1d1c1d; text-align: left;&quot;&gt;1)카테고리 데이터 - 채널, 비디오&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #1d1c1d; text-align: left;&quot;&gt;-CategoryModel을 nextPageToken과 CategoryResponse로 나누어 저장.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #1d1c1d; text-align: left;&quot;&gt;=&amp;gt;새 데이터가 들어오면 토큰은 replace하고 response는 추가하는 방식&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #1d1c1d; text-align: left;&quot;&gt;2) 로그인 데이터&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #1d1c1d; text-align: left;&quot;&gt;-로그인시 로그인 유지를 체크하면 로그인 데이터(아이디, 비밀번호)를 로컬에 저장함&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #1d1c1d; text-align: left;&quot;&gt;-직접 로그아웃시 로그인 데이터를 삭제함&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #1d1c1d; text-align: left;&quot;&gt;-스플래시 화면이 떠있는동안 로그인 데이터를 가져와서 자동로그인 구현&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #1d1c1d; text-align: left;&quot;&gt;2. 리모트 데이터&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #1d1c1d; text-align: left;&quot;&gt;1)파이어베이스&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #1d1c1d; text-align: left;&quot;&gt;&amp;lt;1&amp;gt;유저데이터&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #1d1c1d; text-align: left;&quot;&gt;-회원가입시 중복아이디 체크&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #1d1c1d; text-align: left;&quot;&gt;-유저 데이터 변경시(이름이나 설명), 혹은 새로 회원가입시 수정 및 추가&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #1d1c1d; text-align: left;&quot;&gt;-회원탈퇴시 유저 데이터 삭제&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #1d1c1d; text-align: left;&quot;&gt;-로그인시 유저 데이터 제공&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #1d1c1d; text-align: left;&quot;&gt;&amp;lt;2&amp;gt;유저 좋아요 데이터&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #1d1c1d; text-align: left;&quot;&gt;-좋아요시 추가&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #1d1c1d; text-align: left;&quot;&gt;-좋아요 취소시 삭제&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #1d1c1d; text-align: left;&quot;&gt;-회원가입시 리스트 생성&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #1d1c1d; text-align: left;&quot;&gt;-회원탈퇴시 리스트 삭제&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #1d1c1d; text-align: left;&quot;&gt;-좋아요 리스트 제공&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #1d1c1d; text-align: left;&quot;&gt;2)YoutubeAPI&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #1d1c1d; text-align: left;&quot;&gt;&amp;lt;1&amp;gt;인기 동영상 받아오기&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #1d1c1d; text-align: left;&quot;&gt;&amp;lt;2&amp;gt; 검색 동영상 받아오기&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #1d1c1d; text-align: left;&quot;&gt;&amp;lt;3&amp;gt; 카테고리별 동영상 받아오기&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #1d1c1d; text-align: left;&quot;&gt;&amp;lt;4&amp;gt; 채널아이디로 채널 받아오기&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #1d1c1d; text-align: left;&quot;&gt;@데이터 모델&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #1d1c1d; text-align: left;&quot;&gt;1. 카테고리채널&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #1d1c1d; text-align: left;&quot;&gt; - CategoryChannelModel&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #1d1c1d; text-align: left;&quot;&gt;-nextPageToken: 다음 페이지 검색을 위한 토큰&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #1d1c1d; text-align: left;&quot;&gt;-CategoryChannelResponse: 각 채널별 정보가 담긴 데이터 모델&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #1d1c1d; text-align: left;&quot;&gt;-API에서 사용&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #1d1c1d; text-align: left;&quot;&gt;2. 카테고리비디오 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #1d1c1d; text-align: left;&quot;&gt;- CategoryVideoModel&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #1d1c1d; text-align: left;&quot;&gt;-nextPageToken: 다음 페이지 검색을 위한 토큰&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #1d1c1d; text-align: left;&quot;&gt;-CategoryVideoResponse: 각 비디오별 정보가 담긴 데이터 모델&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #1d1c1d; text-align: left;&quot;&gt;-API에서 사용&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #1d1c1d; text-align: left;&quot;&gt;3. 검색 결과 비디오&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #1d1c1d; text-align: left;&quot;&gt; - SearchResultModel&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #1d1c1d; text-align: left;&quot;&gt;-nextPageToken: 다음 페이지 검색을 위한 토큰&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #1d1c1d; text-align: left;&quot;&gt;-SearchResponse: 검색결과로 나온 각 비디오의 정보가 담긴 데이터 모델&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #1d1c1d; text-align: left;&quot;&gt;-API에서 사용&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #1d1c1d; text-align: left;&quot;&gt;4. 비디오 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #1d1c1d; text-align: left;&quot;&gt;- VideoModel&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #1d1c1d; text-align: left;&quot;&gt;- 상세페이지에 비디오 정보를 띄우기 위해 검색과 카테고리 비디오의 공통 필드를 뽑아 변환한 데이터 모델&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #1d1c1d; text-align: left;&quot;&gt;- Convert를 위한 메소드 작성되어 있음&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #1d1c1d; text-align: left;&quot;&gt;5. 좋아요 리스트 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #1d1c1d; text-align: left;&quot;&gt;-LikeList&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #1d1c1d; text-align: left;&quot;&gt;-VideoModel이 리스트로 담겨있는 클래스&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #1d1c1d; text-align: left;&quot;&gt;-파이어베이스에 사용&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #1d1c1d; text-align: left;&quot;&gt;6. 유저 데이터&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #1d1c1d; text-align: left;&quot;&gt; - User&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #1d1c1d; text-align: left;&quot;&gt;-마이 페이지에 나타낼 정보와 로그인 정보가 담긴 유저 데이터 모델&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #1d1c1d; text-align: left;&quot;&gt;-파이어베이스에서 사용&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #1d1c1d; text-align: left;&quot;&gt;@사용 방식&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #1d1c1d; text-align: left;&quot;&gt;1. 카테고리 데이터&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #1d1c1d; text-align: left;&quot;&gt;1)카테고리 채널과 카테고리 비디오는 sharedpreferences를 체크해서 비어있다면 새로 데이터를 API로 받아옴&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #1d1c1d; text-align: left;&quot;&gt;2)받아온 데이터는 UI에 뿌려주고 비동기로 sharedpreference에 저장&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #1d1c1d; text-align: left;&quot;&gt;3)새로고침시 sharedpreferences는 모든 카테고리에 대하여 전부 clear()&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #1d1c1d; text-align: left;&quot;&gt;2. 인기 동영상, 검색결과 동영상, 카테고리 동영상&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #1d1c1d; text-align: left;&quot;&gt;-받아온 데이터를 VideoModel로 변환&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #1d1c1d; text-align: left;&quot;&gt;-좋아요로 데이터 추가 및 삭제시 파이어베이스에서 추가 및 삭제&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #1d1c1d; text-align: left;&quot;&gt;3. 유저데이터&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #1d1c1d; text-align: left;&quot;&gt;-처음 로그인시 파이어 베이스에서 받아오고 이후에는 intent로 전달&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #1d1c1d; text-align: left;&quot;&gt;@에러 처리 방식&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #1d1c1d; text-align: left;&quot;&gt;1. API와 SharedPreference: Result를 이용해서 에러 핸들링&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #1d1c1d; text-align: left;&quot;&gt;2. firebase: 콜백함수를 이용&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #1d1c1d; text-align: left;&quot;&gt;&amp;gt;스플래시&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #1d1c1d; text-align: left;&quot;&gt;-theme에서 새로 스플래시 테마를 만들어서 처음 액티비티인 startActivity에 적용&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;implementation(&quot;androidx.core:core-splashscreen:1.0.1&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-dependencies설정&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;&amp;lt;style name=&quot;Theme.Splash&quot; parent=&quot;Theme.SplashScreen&quot;&amp;gt;
    &amp;lt;item name=&quot;windowSplashScreenBackground&quot;&amp;gt;@color/black&amp;lt;/item&amp;gt;
    &amp;lt;item name=&quot;windowSplashScreenAnimatedIcon&quot;&amp;gt;@mipmap/ic_app_logo&amp;lt;/item&amp;gt;
    &amp;lt;item name=&quot;windowSplashScreenAnimationDuration&quot;&amp;gt;2000&amp;lt;/item&amp;gt;
    &amp;lt;item name=&quot;postSplashScreenTheme&quot;&amp;gt;@style/Theme.YouTubeProject&amp;lt;/item&amp;gt;

&amp;lt;/style&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-테마 생성&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;&amp;lt;activity
    android:theme=&quot;@style/Theme.Splash&quot;
    android:name=&quot;.presentation.ui.StartActivity&quot;
    android:exported=&quot;true&quot;&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-AndroidManifest에서 테마 지정&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;override fun onCreate(savedInstanceState: Bundle?) {
    installSplashScreen()
    super.onCreate(savedInstanceState)
    enableEdgeToEdge()
    setContentView(R.layout.activity_start)
    supportActionBar?.hide()&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-액티비티에서 메소드 적용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;gt;로그인&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@HiltViewModel
class LoginViewModel @Inject constructor(private val checkLoginUseCase: CheckLoginUseCase, private val cacheLoginDataUseCase: CacheLoginDataUseCase, private val getCacheLoginDataUseCase: GetCacheLoginDataUseCase):ViewModel(){
    private val _uiState = MutableStateFlow&amp;lt;UiState&amp;lt;User&amp;gt;&amp;gt;(UiState.Init)
    val uiState = _uiState.asStateFlow()

    fun loginCheck(id:String, password:String, isCache:Boolean){
        checkLoginUseCase(id,password){ user -&amp;gt;
            if(user!=null){
                var result=true
                if(isCache){
                    result = cacheLoginDataUseCase(id,password)?:false
                }
                if(result){
                    _uiState.value = UiState.Success(user)
                }else{
                    _uiState.value = UiState.Failure(&quot;cachefail&quot;)
                }
            }else{
                _uiState.value = UiState.Failure(&quot;fail&quot;)
            }
        }
    }

    fun autoLogin(){
        val result = getCacheLoginDataUseCase()
        if(result!=null){
            result.let {
                val id = it.first
                val password = it.second
                if(id!=null&amp;amp;&amp;amp;password!=null){
                    loginCheck(id,password,true)
                }else{
                    _uiState.value = UiState.Failure(&quot;failAutoLogin&quot;)
                }
            }
        }else{
            _uiState.value = UiState.Failure(&quot;failAutoLogin&quot;)
        }


    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-자동로그인과 그냥 로그인 메소드를 만들어서 경우에 따라 UiState를 보내준다.&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@AndroidEntryPoint
class StartActivity : AppCompatActivity() {

    private val viewmodel: LoginViewModel by viewModels()


    override fun onCreate(savedInstanceState: Bundle?) {
        installSplashScreen()
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContentView(R.layout.activity_start)
        supportActionBar?.hide()

        lifecycleScope.launch {
            viewmodel.uiState.collectLatest {
                Log.d(&quot;로그&quot;,it.toString())
                if(it is UiState.Init){
                    viewmodel.autoLogin()
                }
                else if(it is UiState.Success){
                    Toast.makeText(this@StartActivity,&quot;로그인 성공&quot;,Toast.LENGTH_SHORT).show()
                    val intent = Intent(this@StartActivity, MainActivity::class.java)
                    intent.putExtra(&quot;userData&quot;,it.data)
                    startActivity(intent)
                    finish()
                }else {
                    supportFragmentManager.beginTransaction().replace(R.id.main, LoginFragment.newInstance()).commit()
                }
            }
        }



    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-startActivity에서 자동로그인시도 후 Uistate를 collect하면서 체크후 실패시 로그인 프래그먼트를 띄움&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-이후 로그인 성공시 여기서 처리&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@AndroidEntryPoint
class LoginFragment : Fragment() {

    private var _binding : FragmentLoginBinding? = null
    private val binding get() = _binding!!

    private val viewmodel:LoginViewModel by activityViewModels()

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

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        initView()
    }

    fun initView() = with(binding){
        viewLifecycleOwner.lifecycleScope.launch {
            viewmodel.uiState.collectLatest {
                when(it){
                    is UiState.Failure -&amp;gt; {
                        if(it.e==&quot;fail&quot;){
                            Toast.makeText(requireContext(),&quot;아이디나 비밀번호를 다시 입력해주세요.&quot;,Toast.LENGTH_SHORT).show()
                        }else if(it.e==&quot;cachefail&quot;){
                            Toast.makeText(requireContext(),&quot;로그인 정보 저장에 실패하였습니다. 다시 시도해주세요.&quot;,Toast.LENGTH_SHORT).show()
                        }
                    }
                    is UiState.Init -&amp;gt; null
                    is UiState.Loading -&amp;gt; null
                    is UiState.Success -&amp;gt; null //액티비티에서 처리
                }
            }
        }

        loginBtnSignup.setOnClickListener {
            parentFragmentManager.beginTransaction().replace(R.id.main, SignUpFragment.newInstance()).addToBackStack(null).commit()
        }
        loginBtnLogin.setOnClickListener {
            val id = loginEtId.text.toString()
            val password = loginEtPassword.text.toString()
            val isCache = loginCbAutologin.isChecked
            viewmodel.loginCheck(id,password,isCache)
        }


    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }

    companion object {

        @JvmStatic
        fun newInstance() = LoginFragment()
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-로그인 시도시 데이터를 뷰모델로 보내주는 역할을 하며 Uistate를 collect하며 실패시 토스트메시지를 띄운다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;gt;회원가입&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@HiltViewModel
class SignUpViewModel @Inject constructor(
    private val checkSignUpUseCase: CheckSignUpUseCase,
    private val registerOrModifyUserDataUseCase: RegisterOrModifyUserDataUseCase,
    private val uploadProfileUseCase: UploadProfileUseCase,
    private val createLikeListUseCase: CreateLikeListUseCase
):ViewModel() {
    private val _uiState = MutableStateFlow&amp;lt;UiState&amp;lt;Unit&amp;gt;&amp;gt;(UiState.Init)
    val uiState = _uiState.asStateFlow()

    fun signUp(id:String, password:String, name:String, intro:String, profile:Bitmap?){
        checkSignUpUseCase(id,password,name){ notify,isEnable -&amp;gt;
            if(isEnable){
                createLikeListUseCase(id){ likelist-&amp;gt;
                    if(likelist){
                        var user = User(null,name, id, password, intro)
                        if(profile!=null){
                            uploadProfileUseCase(profile,id){ url -&amp;gt;
                                if(url==null){
                                    _uiState.value = UiState.Failure(&quot;N&quot;+notify)
                                }else{
                                    registerOrModifyUserDataUseCase(user.copy(profile = url)){
                                        if(it){
                                            _uiState.value = UiState.Success(Unit)
                                        }else{
                                            _uiState.value = UiState.Failure(&quot;F&quot;+notify)
                                        }
                                    }
                                }
                            }
                        }else{
                            registerOrModifyUserDataUseCase(user){
                                if(it){
                                    _uiState.value = UiState.Success(Unit)
                                }else{
                                    _uiState.value = UiState.Failure(&quot;F&quot;+notify)
                                }
                            }
                        }
                    }else{
                        _uiState.value = UiState.Failure(&quot;Lfail to make likelist&quot;)
                    }
                }
            }else{
                _uiState.value = UiState.Failure(&quot;N&quot;+notify)
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-회원가입시도시 아이디와 패스워드와 이름을 먼저 체크 하고 이후 좋아요 리스트 생성 성공 여부 이후 프로필이미지 업로드후 마지막으로 유저 데이터를 등록하며 마무리된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-경우의 따라 uistate를 보내줌.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@AndroidEntryPoint
class SignUpFragment : Fragment() {

    private var _binding : FragmentSignUpBinding? = null
    private val binding get() = _binding!!

    val viewmodel:SignUpViewModel by viewModels()

    private var profile: Bitmap? = null


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

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        initView()
    }

    fun initView() = with(binding){

        viewLifecycleOwner.lifecycleScope.launch {
            viewmodel.uiState.collectLatest {
                when(it){
                    is UiState.Init -&amp;gt; null
                    is UiState.Failure -&amp;gt; {
                        if(it.e[0]!='N')
                        Toast.makeText(requireActivity(), &quot;오류로 인해 등록에 실패하였습니다. 다시시도 해주세요.&quot;,Toast.LENGTH_SHORT).show()
                        signupTvNotify.visibility=View.VISIBLE
                        signupTvNotify.text = it.e.substring(1)
                    }
                    is UiState.Success -&amp;gt; {
                        Toast.makeText(requireActivity(),&quot;회원가입 성공&quot;,Toast.LENGTH_SHORT).show()
                        parentFragmentManager.beginTransaction().replace(R.id.main,
                            LoginFragment.newInstance()
                        ).commit()
                    }
                    is UiState.Loading -&amp;gt; null
                }
            }
        }

        signupBtnSignup.setOnClickListener {
            val id=signupEtId.text.toString()
            val password = signupEtPassword.text.toString()
            val name = signupEtName.text.toString()
            val intro = signupEtIntro.text.toString()

            viewmodel.signUp(id, password, name, intro,profile)
        }

        val phohtoPicker =registerForActivityResult(ActivityResultContracts.PickVisualMedia()){ uri -&amp;gt;
            if(uri !=null){
                Toast.makeText(requireActivity(), &quot;길게 클릭시 프로필 이미지가 초기화됩니다.&quot;, Toast.LENGTH_SHORT).show()
                val result = runCatching {
                    BitmapFactory.decodeStream(requireActivity().contentResolver.openInputStream(uri))
                }
                profile = result.getOrNull()
                profile?.let {
                    signupIvProfile.setImageBitmap(it)
                }
            }else{
                profile = null
                signupIvProfile.setImageResource(R.drawable.ic_person)
            }
        }

        signupIvProfile.setOnClickListener {
            phohtoPicker.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly))
        }

        signupIvProfile.setOnLongClickListener {
            profile = null
            signupIvProfile.setImageResource(R.drawable.ic_person)
            return@setOnLongClickListener true
        }


    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }

    companion object {

        @JvmStatic
        fun newInstance() = SignUpFragment()
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>코틀린 팀플4-유튜브api활용 어플 만들기</category>
      <author>songyooho</author>
      <guid isPermaLink="true">https://appdevelopjava.tistory.com/95</guid>
      <comments>https://appdevelopjava.tistory.com/95#entry95comment</comments>
      <pubDate>Mon, 19 Aug 2024 22:00:32 +0900</pubDate>
    </item>
    <item>
      <title>69일차)챌린지반 강의(Json, 비동기 프로그래밍), Compose강의(LaunchedEffect, rememberCoroutineScope, rememberUpdatedState, DisposableEffect, rememberLauncherForActivityResult)</title>
      <link>https://appdevelopjava.tistory.com/94</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;gt;챌린지반 강의&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1.&amp;nbsp;Json(JavaScript&amp;nbsp;Object&amp;nbsp;Notation) &lt;br /&gt;1)Client&amp;nbsp;-&amp;nbsp;REST&amp;nbsp;API&amp;nbsp;server간&amp;nbsp;통신 &lt;br /&gt;-예전엔&amp;nbsp;HTML코드로&amp;nbsp;통신 &lt;br /&gt;=&amp;gt;문제점:&amp;nbsp;웹브라우저는&amp;nbsp;이해되는데&amp;nbsp;안드로이드같은&amp;nbsp;곳에선&amp;nbsp;알아볼&amp;nbsp;수&amp;nbsp;없음 &lt;br /&gt;-지금은&amp;nbsp;Json으로&amp;nbsp;통신 &lt;br /&gt;=&amp;gt;웹브라우저뿐만이&amp;nbsp;아니라&amp;nbsp;다른&amp;nbsp;OS들도&amp;nbsp;데이터를&amp;nbsp;인식하기&amp;nbsp;쉬워짐 &lt;br /&gt;&lt;br /&gt;2)구조&amp;nbsp;:&amp;nbsp;Key&amp;nbsp;-&amp;nbsp;Value&amp;nbsp;형태 &lt;br /&gt;-{&amp;nbsp;}&amp;nbsp;는&amp;nbsp;Object &lt;br /&gt;-[&amp;nbsp;]&amp;nbsp;는&amp;nbsp;Array &lt;br /&gt;&lt;br /&gt;3)convert에&amp;nbsp;사용할&amp;nbsp;dataclass &lt;br /&gt;-&quot;reponse&quot;&amp;nbsp;:&amp;nbsp;{&amp;nbsp;}&amp;nbsp;=&amp;gt;&amp;nbsp;val&amp;nbsp;Reponse:&amp;nbsp;Body&amp;nbsp;//오브젝트는&amp;nbsp;해당&amp;nbsp;부분을&amp;nbsp;데이터&amp;nbsp;클래스로 &lt;br /&gt;@최상위에&amp;nbsp;{&amp;nbsp;}로&amp;nbsp;시작하면&amp;nbsp;해당&amp;nbsp;오브젝트에&amp;nbsp;대응되는&amp;nbsp;data&amp;nbsp;class를&amp;nbsp;만들어야&amp;nbsp;한다. &lt;br /&gt;-&quot;List&quot;&amp;nbsp;:&amp;nbsp;[&amp;nbsp;]&amp;nbsp;=&amp;gt;&amp;nbsp;val&amp;nbsp;List:&amp;nbsp;List&amp;lt;~&amp;gt;&amp;nbsp;//&amp;nbsp;리스트는&amp;nbsp;리스트&amp;nbsp;형태로 &lt;br /&gt;-type:&amp;nbsp;nullable인지&amp;nbsp;nonNull&amp;nbsp;인지&amp;nbsp;알아야&amp;nbsp;한다. &lt;br /&gt;&lt;br /&gt;2.&amp;nbsp;비동기&amp;nbsp;프로그래밍 &lt;br /&gt;1)배터리&amp;nbsp;절약:&amp;nbsp;실행시간을&amp;nbsp;줄이고&amp;nbsp;HTTP&amp;nbsp;연결을&amp;nbsp;줄이는것 &lt;br /&gt;=&amp;gt;비동기&amp;nbsp;프로그래밍을&amp;nbsp;하면&amp;nbsp;실행시간이&amp;nbsp;줄어들음 &lt;br /&gt;&lt;br /&gt;2)async &lt;br /&gt;-val&amp;nbsp;lists&amp;nbsp;=&amp;nbsp;listOf(&amp;nbsp;async{foo1()},&amp;nbsp;async{&amp;nbsp;foo2()},&amp;nbsp;...) &lt;br /&gt;=&amp;gt;val&amp;nbsp;data&amp;nbsp;=&amp;nbsp;lists.awaitAll()하면&amp;nbsp;한번에&amp;nbsp;처리&amp;nbsp;가능 &lt;br /&gt;&lt;br /&gt;-async&amp;nbsp;블록의&amp;nbsp;반환값:&amp;nbsp;마지막&amp;nbsp;라인으로&amp;nbsp;해도&amp;nbsp;되지만&amp;nbsp;return@async로&amp;nbsp;해도&amp;nbsp;됨 &lt;br /&gt;=&amp;gt;async{&amp;nbsp;return@async&amp;nbsp;~&amp;nbsp;}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;gt;Compose강의&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 사전 정리내용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-LaunchedEffect &lt;br /&gt;1)구조:&amp;nbsp;LaunchEffect(key)&amp;nbsp;//한번만&amp;nbsp;실행시키고&amp;nbsp;싶으면&amp;nbsp;key에&amp;nbsp;Unit &lt;br /&gt;2)작동방식:&amp;nbsp;composable이&amp;nbsp;처음&amp;nbsp;구성될때&amp;nbsp;실행되며&amp;nbsp;key값이&amp;nbsp;변경될&amp;nbsp;때마다&amp;nbsp;다시&amp;nbsp;실행됨 &lt;br /&gt;=&amp;gt;Recomposition이&amp;nbsp;일어나도&amp;nbsp;key값이&amp;nbsp;그대로면&amp;nbsp;재실행되지&amp;nbsp;않음 &lt;br /&gt;3)특징:&amp;nbsp;내부에&amp;nbsp;코루틴을&amp;nbsp;사용할&amp;nbsp;수&amp;nbsp;있음 &lt;br /&gt;4)주요&amp;nbsp;사용&amp;nbsp;예시 &lt;br /&gt;-초기&amp;nbsp;데이터&amp;nbsp;가져오기:&amp;nbsp;화면&amp;nbsp;처음구성시&amp;nbsp;데이터&amp;nbsp;불러오거나&amp;nbsp;초기화 &lt;br /&gt;-비동기&amp;nbsp;작업:&amp;nbsp;네트워크&amp;nbsp;요청,&amp;nbsp;데이터베이스&amp;nbsp;호출=&amp;gt;근데&amp;nbsp;이건&amp;nbsp;뷰모델이나&amp;nbsp;도메인에서 &lt;br /&gt;=&amp;gt;왜냐하면&amp;nbsp;composable에선&amp;nbsp;UI작업을&amp;nbsp;해야하지&amp;nbsp;다른&amp;nbsp;작업을&amp;nbsp;하면&amp;nbsp;안됨. &lt;br /&gt;-애니메이션&amp;nbsp;시작 &lt;br /&gt;@예시 &lt;br /&gt;var&amp;nbsp;pulseRateMs&amp;nbsp;by&amp;nbsp;remember&amp;nbsp;{&amp;nbsp;mutableStateOf(3000L)&amp;nbsp;} &lt;br /&gt;val&amp;nbsp;alpha&amp;nbsp;=&amp;nbsp;remember&amp;nbsp;{&amp;nbsp;Animatable(1f)&amp;nbsp;} &lt;br /&gt;LaunchedEffect(pulseRateMs)&amp;nbsp;{&amp;nbsp;//&amp;nbsp;Restart&amp;nbsp;the&amp;nbsp;effect&amp;nbsp;when&amp;nbsp;the&amp;nbsp;pulse&amp;nbsp;rate&amp;nbsp;changes &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;while&amp;nbsp;(isActive)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;delay(pulseRateMs)&amp;nbsp;//&amp;nbsp;Pulse&amp;nbsp;the&amp;nbsp;alpha&amp;nbsp;every&amp;nbsp;pulseRateMs&amp;nbsp;to&amp;nbsp;alert&amp;nbsp;the&amp;nbsp;user &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;alpha.animateTo(0f) &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;alpha.animateTo(1f) &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;} &lt;br /&gt;5)유의점:&amp;nbsp;LaunchedEffect는&amp;nbsp;composable함수이므로&amp;nbsp;Composable함수&amp;nbsp;내에서만&amp;nbsp;사용&amp;nbsp;가능 &lt;br /&gt;&lt;br /&gt;-rememberCoroutineScope &lt;br /&gt;1)사용:&amp;nbsp; &lt;br /&gt;-컴포저블&amp;nbsp;외부에&amp;nbsp;있으면서&amp;nbsp;컴포지션&amp;nbsp;종료후&amp;nbsp;자동으로&amp;nbsp;취소되도록&amp;nbsp;범위가&amp;nbsp;지정된&amp;nbsp;코루틴&amp;nbsp;실행 &lt;br /&gt;-코루틴의&amp;nbsp;수명주기를&amp;nbsp;수동으로&amp;nbsp;관리할시 &lt;br /&gt;=&amp;gt;사용자&amp;nbsp;이벤트&amp;nbsp;발생시&amp;nbsp;애니메이션&amp;nbsp;취소해야&amp;nbsp;하는&amp;nbsp;경우 &lt;br /&gt;2)특징:&amp;nbsp; &lt;br /&gt;-수명주기가&amp;nbsp;컴포저블의&amp;nbsp;라이프&amp;nbsp;사이클과&amp;nbsp;일치 &lt;br /&gt;=&amp;gt;컴포저블이&amp;nbsp;화면에서&amp;nbsp;제거되면&amp;nbsp;코루틴도&amp;nbsp;취소 &lt;br /&gt;-recomposition에&amp;nbsp;영향을&amp;nbsp;받지&amp;nbsp;않음:&amp;nbsp;recomposition이&amp;nbsp;발생해도&amp;nbsp;스코프가&amp;nbsp;유지되어&amp;nbsp;중복실행이나&amp;nbsp;리소스낭비&amp;nbsp;방지 &lt;br /&gt;3)주요&amp;nbsp;사용 &lt;br /&gt;-사용자&amp;nbsp;상호작용처리:&amp;nbsp;버튼&amp;nbsp;클릭.&amp;nbsp;스크롤등&amp;nbsp;상호작용에&amp;nbsp;따른&amp;nbsp;비동기&amp;nbsp;작업&amp;nbsp;처리시 &lt;br /&gt;@예시 &lt;br /&gt;@Composable &lt;br /&gt;fun&amp;nbsp;MoviesScreen(snackbarHostState:&amp;nbsp;SnackbarHostState)&amp;nbsp;{ &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;Creates&amp;nbsp;a&amp;nbsp;CoroutineScope&amp;nbsp;bound&amp;nbsp;to&amp;nbsp;the&amp;nbsp;MoviesScreen's&amp;nbsp;lifecycle &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val&amp;nbsp;scope&amp;nbsp;=&amp;nbsp;rememberCoroutineScope() &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Scaffold( &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;snackbarHost&amp;nbsp;=&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SnackbarHost(hostState&amp;nbsp;=&amp;nbsp;snackbarHostState) &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)&amp;nbsp;{&amp;nbsp;contentPadding&amp;nbsp;-&amp;gt; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Column(Modifier.padding(contentPadding))&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Button( &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;onClick&amp;nbsp;=&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;Create&amp;nbsp;a&amp;nbsp;new&amp;nbsp;coroutine&amp;nbsp;in&amp;nbsp;the&amp;nbsp;event&amp;nbsp;handler&amp;nbsp;to&amp;nbsp;show&amp;nbsp;a&amp;nbsp;snackbar &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;scope.launch&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;snackbarHostState.showSnackbar(&quot;Something&amp;nbsp;happened!&quot;) &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Text(&quot;Press&amp;nbsp;me&quot;) &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;} &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;-rememberUpdatedState &lt;br /&gt;1)사용:&amp;nbsp;코루틴,&amp;nbsp;타이머,&amp;nbsp;리스터와&amp;nbsp;같은&amp;nbsp;비동기&amp;nbsp;작업이나&amp;nbsp;오래&amp;nbsp;실행되는&amp;nbsp;작업에서&amp;nbsp;Recomposition이&amp;nbsp;일어나더라도&amp;nbsp;최신&amp;nbsp;상태를&amp;nbsp;안전하게&amp;nbsp;참조하기&amp;nbsp;위해&amp;nbsp;사용하는&amp;nbsp;것 &lt;br /&gt;2)예시 &lt;br /&gt;-문제&amp;nbsp;발생&amp;nbsp;상황:&amp;nbsp;비동기&amp;nbsp;작업에서&amp;nbsp;이전&amp;nbsp;상태를&amp;nbsp;참조하는&amp;nbsp;경우 &lt;br /&gt;@Composable &lt;br /&gt;fun&amp;nbsp;Timer(onTimeout:&amp;nbsp;()&amp;nbsp;-&amp;gt;&amp;nbsp;Unit,&amp;nbsp;timeoutDuration:&amp;nbsp;Long)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;LaunchedEffect(timeoutDuration)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;delay(timeoutDuration) &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;onTimeout()&amp;nbsp;//&amp;nbsp;이&amp;nbsp;시점에서&amp;nbsp;onTimeout이&amp;nbsp;오래된&amp;nbsp;참조일&amp;nbsp;수&amp;nbsp;있음 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Text(&quot;Waiting&amp;nbsp;for&amp;nbsp;$timeoutDuration&amp;nbsp;milliseconds...&quot;) &lt;br /&gt;} &lt;br /&gt;=&amp;gt;onTimeout이&amp;nbsp;바뀌어&amp;nbsp;리컴포지션이&amp;nbsp;일어나더라도&amp;nbsp;LaunchedEffect는&amp;nbsp;키값의&amp;nbsp;변화가&amp;nbsp;아니면&amp;nbsp;영향을&amp;nbsp;받지&amp;nbsp;않으므로&amp;nbsp;나중에&amp;nbsp;실행할때&amp;nbsp;오래된&amp;nbsp;onTimeout객체를&amp;nbsp;참조하고&amp;nbsp;있는&amp;nbsp;문제가&amp;nbsp;발생 &lt;br /&gt;&lt;br /&gt;-해결 &lt;br /&gt;@Composable &lt;br /&gt;fun&amp;nbsp;Timer(onTimeout:&amp;nbsp;()&amp;nbsp;-&amp;gt;&amp;nbsp;Unit,&amp;nbsp;timeoutDuration:&amp;nbsp;Long)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;최신&amp;nbsp;onTimeout&amp;nbsp;콜백&amp;nbsp;참조를&amp;nbsp;기억 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val&amp;nbsp;updatedOnTimeout&amp;nbsp;by&amp;nbsp;rememberUpdatedState(onTimeout) &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;LaunchedEffect(timeoutDuration)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;delay(timeoutDuration) &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;updatedOnTimeout()&amp;nbsp;//&amp;nbsp;항상&amp;nbsp;최신의&amp;nbsp;onTimeout을&amp;nbsp;호출 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Text(&quot;Waiting&amp;nbsp;for&amp;nbsp;$timeoutDuration&amp;nbsp;milliseconds...&quot;) &lt;br /&gt;} &lt;br /&gt;=&amp;gt;rememberUpdatedState()을&amp;nbsp;사용하면&amp;nbsp;해당&amp;nbsp;값이&amp;nbsp;최신객체를&amp;nbsp;참조하고&amp;nbsp;있도록&amp;nbsp;해줌 &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;-DisposableEffect:&amp;nbsp;Composable의&amp;nbsp;lifecycle에&amp;nbsp;맞춰&amp;nbsp;리소스&amp;nbsp;정리가&amp;nbsp;필요할때&amp;nbsp;사용 &lt;br /&gt;1)구조: &lt;br /&gt;DisposableEffect(key){ &lt;br /&gt;. &lt;br /&gt;. &lt;br /&gt;. &lt;br /&gt;onDispose{&amp;nbsp;~&amp;nbsp;} &lt;br /&gt;} &lt;br /&gt;2)작동:&amp;nbsp; &lt;br /&gt;-DisposableEffect블록:&amp;nbsp;처음&amp;nbsp;Composition시와&amp;nbsp;key값이&amp;nbsp;바뀔때마다&amp;nbsp;실행됨 &lt;br /&gt;-onDispose블록:&amp;nbsp;key값이&amp;nbsp;바뀔때와&amp;nbsp;Composable종료(컴포저블이&amp;nbsp;화면에서&amp;nbsp;제거되거나&amp;nbsp;컴포지션&amp;nbsp;종료)시&amp;nbsp;실행됨 &lt;br /&gt;3)사용 &lt;br /&gt;-컴포지션&amp;nbsp;시작시&amp;nbsp;리소스&amp;nbsp;초기화하고&amp;nbsp;컴포지션&amp;nbsp;종료시&amp;nbsp;정리작업이&amp;nbsp;필요할때 &lt;br /&gt;-key값이&amp;nbsp;변경될때마다&amp;nbsp;기존&amp;nbsp;리소스를&amp;nbsp;정리하고&amp;nbsp;새&amp;nbsp;리소스&amp;nbsp;초기화가&amp;nbsp;필요할때 &lt;br /&gt;@여기서&amp;nbsp;리소스에는&amp;nbsp;리스너&amp;nbsp;같은것도&amp;nbsp;포함 &lt;br /&gt;&lt;br /&gt;4)예시 &lt;br /&gt;@Composable &lt;br /&gt;fun&amp;nbsp;MyComposable(lifecycleOwner:&amp;nbsp;LifecycleOwner)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;DisposableEffect를&amp;nbsp;사용하여&amp;nbsp;생명주기&amp;nbsp;관찰자&amp;nbsp;등록 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;DisposableEffect(lifecycleOwner)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val&amp;nbsp;observer&amp;nbsp;=&amp;nbsp;object&amp;nbsp;:&amp;nbsp;LifecycleObserver&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@OnLifecycleEvent(Lifecycle.Event.ON_RESUME) &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;fun&amp;nbsp;onResume()&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;println(&quot;ON_RESUME&amp;nbsp;이벤트&amp;nbsp;발생&quot;) &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;fun&amp;nbsp;onPause()&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;println(&quot;ON_PAUSE&amp;nbsp;이벤트&amp;nbsp;발생&quot;) &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;생명주기&amp;nbsp;관찰자&amp;nbsp;등록 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lifecycleOwner.lifecycle.addObserver(observer) &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;컴포지션이&amp;nbsp;종료되거나&amp;nbsp;key가&amp;nbsp;변경될&amp;nbsp;때&amp;nbsp;호출되어&amp;nbsp;관찰자&amp;nbsp;해제 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;onDispose&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lifecycleOwner.lifecycle.removeObserver(observer) &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;println(&quot;관찰자&amp;nbsp;해제됨&quot;) &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;UI&amp;nbsp;코드 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Text(&quot;Lifecycle&amp;nbsp;aware&amp;nbsp;component&quot;) &lt;br /&gt;} &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 강의 정리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-단발성 이벤트 전달은 sharedFlow로 주로 함 &lt;br /&gt;-컴포서블의 라이프사이클오너 받는법: LocalLifecycleOwner.current &lt;br /&gt;=&amp;gt;라이프&amp;nbsp;라이클의&amp;nbsp;특정&amp;nbsp;시점마다&amp;nbsp;받복&amp;nbsp;실행:&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;lifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.~){ &lt;br /&gt;&amp;nbsp; &amp;nbsp; launch{ ~ } &lt;br /&gt;} &lt;br /&gt;=&amp;gt;코루틴&amp;nbsp;스코프내에서&amp;nbsp;사용가능 &lt;br /&gt;&lt;br /&gt;-컴포스에선 AlertDialog로 다이얼로그 띄움 &lt;br /&gt;&lt;br /&gt;-ActivityforResult은 rememberLauncherForActivityResult &lt;br /&gt;&lt;br /&gt;-find{} 조건에 맞는게 있으면 원소를 찾고 아니면 null&lt;/p&gt;</description>
      <category>코틀린-안드로이드</category>
      <author>songyooho</author>
      <guid isPermaLink="true">https://appdevelopjava.tistory.com/94</guid>
      <comments>https://appdevelopjava.tistory.com/94#entry94comment</comments>
      <pubDate>Wed, 14 Aug 2024 20:15:16 +0900</pubDate>
    </item>
    <item>
      <title>68일차)알고리즘 문제(퍼즐 조각 채우기), 특강(Room, 인터페이스로 데이터 동기화, adapter MVVM구조에서 유의점, 인터페이스를 이용한 데이터 동기화)</title>
      <link>https://appdevelopjava.tistory.com/93</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;gt;알고리즘 문제&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 문제&lt;/p&gt;
&lt;p style=&quot;background-color: #263747; color: #b2c0cc; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;테이블 위에 놓인 퍼즐 조각을 게임 보드의 빈 공간에 적절히 올려놓으려 합니다. 게임 보드와 테이블은 모두 각 칸이 1x1 크기인 정사각 격자 모양입니다. 이때, 다음 규칙에 따라 테이블 위에 놓인 퍼즐 조각을 게임 보드의 빈칸에 채우면 됩니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #263747; color: #b2c0cc; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;조각은 한 번에 하나씩 채워 넣습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;조각을 회전시킬 수 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;조각을 뒤집을 수는 없습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;게임 보드에 새로 채워 넣은 퍼즐 조각과 인접한 칸이 비어있으면 안 됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #263747; color: #b2c0cc; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;다음은 퍼즐 조각을 채우는 예시입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;566&quot; data-origin-height=&quot;289&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SkVTD/btsIZJBHpJg/tkKEttxKkootlHmdOrJeR1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SkVTD/btsIZJBHpJg/tkKEttxKkootlHmdOrJeR1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SkVTD/btsIZJBHpJg/tkKEttxKkootlHmdOrJeR1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSkVTD%2FbtsIZJBHpJg%2FtkKEttxKkootlHmdOrJeR1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;566&quot; height=&quot;289&quot; data-origin-width=&quot;566&quot; data-origin-height=&quot;289&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #263747; color: #b2c0cc; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;위 그림에서 왼쪽은 현재 게임 보드의 상태를, 오른쪽은 테이블 위에 놓인 퍼즐 조각들을 나타냅니다. 테이블 위에 놓인 퍼즐 조각들 또한 마찬가지로 [상,하,좌,우]로 인접해 붙어있는 경우는 없으며, 흰 칸은 퍼즐이 놓이지 않은 빈 공간을 나타냅니다. 모든 퍼즐 조각은 격자 칸에 딱 맞게 놓여있으며, 격자 칸을 벗어나거나, 걸쳐 있는 등 잘못 놓인 경우는 없습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #263747; color: #b2c0cc; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;이때, 아래 그림과 같이 3,4,5번 조각을 격자 칸에 놓으면 규칙에 어긋나므로 불가능한 경우입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;569&quot; data-origin-height=&quot;289&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rwksO/btsIZzsiPZT/LPzyyQ6RkJbxTbNwBiPPq0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rwksO/btsIZzsiPZT/LPzyyQ6RkJbxTbNwBiPPq0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rwksO/btsIZzsiPZT/LPzyyQ6RkJbxTbNwBiPPq0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrwksO%2FbtsIZzsiPZT%2FLPzyyQ6RkJbxTbNwBiPPq0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;569&quot; height=&quot;289&quot; data-origin-width=&quot;569&quot; data-origin-height=&quot;289&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #263747; color: #b2c0cc; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;3번 조각을 놓고 4번 조각을 놓기 전에 위쪽으로 인접한 칸에 빈칸이 생깁니다.&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;5번 조각의 양 옆으로 인접한 칸에 빈칸이 생깁니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #263747; color: #b2c0cc; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;다음은 규칙에 맞게 최대한 많은 조각을 게임 보드에 채워 넣은 모습입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;567&quot; data-origin-height=&quot;282&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/E11d3/btsI0dPMJBN/OHxuNnlHeRoRkNJ7YF6rJ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/E11d3/btsI0dPMJBN/OHxuNnlHeRoRkNJ7YF6rJ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/E11d3/btsI0dPMJBN/OHxuNnlHeRoRkNJ7YF6rJ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FE11d3%2FbtsI0dPMJBN%2FOHxuNnlHeRoRkNJ7YF6rJ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;567&quot; height=&quot;282&quot; data-origin-width=&quot;567&quot; data-origin-height=&quot;282&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #263747; color: #b2c0cc; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;최대한 많은 조각을 채워 넣으면 총 14칸을 채울 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #263747; color: #b2c0cc; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;현재 게임 보드의 상태&lt;span&gt;&amp;nbsp;&lt;/span&gt;game_board, 테이블 위에 놓인 퍼즐 조각의 상태&lt;span&gt;&amp;nbsp;&lt;/span&gt;table이 매개변수로 주어집니다. 규칙에 맞게 최대한 많은 퍼즐 조각을 채워 넣을 경우, 총 몇 칸을 채울 수 있는지 return 하도록 solution 함수를 완성해주세요.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제한사항&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #263747; color: #b2c0cc; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;3 &amp;le;&amp;nbsp;game_board의 행 길이 &amp;le; 50&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;game_board의 각 열 길이 =&amp;nbsp;game_board의 행 길이&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc; color: #000000;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;즉, 게임 보드는 정사각 격자 모양입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;game_board의 모든 원소는 0 또는 1입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;0은 빈칸, 1은 이미 채워진 칸을 나타냅니다.&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;퍼즐 조각이 놓일 빈칸은 1 x 1 크기 정사각형이 최소 1개에서 최대 6개까지 연결된 형태로만 주어집니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;table의 행 길이 =&amp;nbsp;game_board의 행 길이&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;table의 각 열 길이 =&amp;nbsp;table의 행 길이&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc; color: #000000;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;즉, 테이블은&amp;nbsp;game_board와 같은 크기의 정사각 격자 모양입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;table의 모든 원소는 0 또는 1입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;0은 빈칸, 1은 조각이 놓인 칸을 나타냅니다.&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;퍼즐 조각은 1 x 1 크기 정사각형이 최소 1개에서 최대 6개까지 연결된 형태로만 주어집니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;game_board에는 반드시 하나 이상의 빈칸이 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;table에는 반드시 하나 이상의 블록이 놓여 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 솔루션&lt;/p&gt;
&lt;pre id=&quot;code_1723201154727&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Solution {
    fun solution(game_board: Array&amp;lt;IntArray&amp;gt;, table: Array&amp;lt;IntArray&amp;gt;): Int {
        var answer: Int = 0
        
        val size=table.size
        
        
        val gamePiece=BFS(0,game_board)
        val tablePiece=BFS(1,table)
        
        val gamePieceWithRotationIdx=Array(gamePiece.size){HashSet&amp;lt;List&amp;lt;Int&amp;gt;&amp;gt;()}
        val tablePieceIdx=tablePiece.map{indexing(it,size)}
        
        for(i in 0..gamePiece.size-1){
            for(j in 0..3){
                gamePieceWithRotationIdx[i]+=indexing(gamePiece[i],size)
                rotation(gamePiece[i])
            }
        }
        
        
        
        
        val isUsed=BooleanArray(gamePieceWithRotationIdx.size){false}
        
        for(i in tablePieceIdx){
            for((idx,j) in gamePieceWithRotationIdx.withIndex()){
                if(isUsed[idx]) continue
                var flag=false
                for(k in j){
                    if(i==k&amp;amp;&amp;amp;!isUsed[idx]){
                        flag=true
                        answer+=i.size
                        isUsed[idx]=true
                        break
                    }
                }
                if(flag) break
            
            }
        }
        
        
        return answer
    }
}

fun indexing(piece:Piece, size:Int):List&amp;lt;Int&amp;gt;{
    val result=HashSet&amp;lt;Int&amp;gt;()
    
    val visited=HashSet&amp;lt;Block&amp;gt;()
    
    val q=ArrayDeque&amp;lt;Pair&amp;lt;Int,Block&amp;gt;&amp;gt;()
    q.addLast(Pair(0,piece.blocks[0]))
    visited+=piece.blocks[0]
    
    while(q.isNotEmpty()){
        val (idx,b)=q.removeFirst()
        result+=idx
        
        for(i in 0..3){
            b.adj[i]?.let{
                if(!visited.contains(it)){
                    when(i){
                        0 -&amp;gt; q.addLast(Pair(idx-size,it))
                        1 -&amp;gt; q.addLast(Pair(idx+1,it))
                        2 -&amp;gt; q.addLast(Pair(idx+size,it))
                        else -&amp;gt; q.addLast(Pair(idx-1,it))
                    }
                    visited+=it
                }
            }
        }
    }
    return result.sorted().let{it.map{b -&amp;gt; b-it[0]}}
}

fun blockRotation(block:Block){
    block.adj.also{
        val tmp=it[0]
        it[0]=it[1]
        it[1]=it[2]
        it[2]=it[3]
        it[3]=tmp
    }
}

fun rotation(piece:Piece){
    for(i in piece.blocks){
        blockRotation(i)
    }
}

fun BFS(type:Int, arr:Array&amp;lt;IntArray&amp;gt;):ArrayList&amp;lt;Piece&amp;gt;{
    val size=arr.size
    val visited=BooleanArray(size*size){false}
    val result=ArrayList&amp;lt;Piece&amp;gt;()
    
    for(i in arr.indices){
        for(j in arr.indices){
            if(arr[i][j]!=type){
                visited[i*size+j]=true
            }
        }
    }
    
    val q=ArrayDeque&amp;lt;Triple&amp;lt;Int,Block,Piece&amp;gt;&amp;gt;()
    
    var initIdx=0
    
    for(i in visited.indices){
        if(!visited[i]){
            visited[i]=true
            initIdx=i
            break
        }
    }
    
    val piece=Piece()
    val block=Block()
    piece.blocks+=block
    result+=piece
    
    q.addLast(Triple(initIdx,block,piece))
    
    val dx=listOf(0,0,1,-1)
    val dy=listOf(1,-1,0,0)
    
    while(q.isNotEmpty()){
        val (idx,b,p)=q.removeFirst()
        
        val x=idx/size
        val y=idx%size
        
        for(i in 0..3){
            val nx=x+dx[i]
            val ny=y+dy[i]
            if(nx !in 0..size-1||ny !in 0..size-1) continue
            val nextIdx=nx*size+ny
            if(!visited[nextIdx]){
                visited[nextIdx]=true
                val newB=Block()
                p.blocks+=newB
                when(i){
                    0 -&amp;gt;{
                        b.adj[2]=newB
                        newB.adj[0]=b
                    }
                    1 -&amp;gt;{
                        b.adj[0]=newB
                        newB.adj[2]=b
                    }
                    2-&amp;gt;{
                        b.adj[1]=newB
                        newB.adj[3]=b
                    }
                    else-&amp;gt;{
                        b.adj[3]=newB
                        newB.adj[1]=b
                    }
                }
                q.addLast(Triple(nextIdx,newB,p))
            }
        }
        
        if(q.isEmpty()){
            for(i in visited.indices){
                if(!visited[i]){
                    visited[i]=true
                    val piece=Piece()
                    val block=Block()
                    piece.blocks+=block
                    result+=piece
                    q.addLast(Triple(i,block,piece))
                    break
                }
            }
        } 
    }
    return result
}


class Block( ){
    val adj:MutableList&amp;lt;Block?&amp;gt; =mutableListOf(null,null,null,null)//위 오른 아래 왼
}

class Piece(){
    val blocks=ArrayList&amp;lt;Block&amp;gt;()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-BFS로 각 Piece들을 구함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-table과 game에 대한 piece모음을 구함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-game에 대하여 각 piece에 대해 rotation을 돌린 경우도 모아둠&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-각 piece에 대해 indexing을 하여 결과를 비교함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;gt;특강&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1.&amp;nbsp;Room:&amp;nbsp;스마트폰&amp;nbsp;내장&amp;nbsp;DB에&amp;nbsp;데이터&amp;nbsp;저장을&amp;nbsp;위한&amp;nbsp;라이브러리 &lt;br /&gt;1)세가지&amp;nbsp;요소 &lt;br /&gt;-Database:DB객체를&amp;nbsp;만들기&amp;nbsp;위한&amp;nbsp;추상&amp;nbsp;클래스 &lt;br /&gt;-Entity:&amp;nbsp;table &lt;br /&gt;-DAO:&amp;nbsp;쿼리 &lt;br /&gt;&lt;br /&gt;-DB는&amp;nbsp;Application의&amp;nbsp;Context가&amp;nbsp;필요 &lt;br /&gt;&lt;br /&gt;@Context받는것&amp;nbsp;유의점:&amp;nbsp;Context는&amp;nbsp;받은&amp;nbsp;스코프의&amp;nbsp;수명이&amp;nbsp;끝나면&amp;nbsp;context도&amp;nbsp;파기됨 &lt;br /&gt;=&amp;gt;프래그먼트에서&amp;nbsp;받으면&amp;nbsp;프래그먼트&amp;nbsp;수명이&amp;nbsp;끝나면&amp;nbsp;context도&amp;nbsp;파기됨 &lt;br /&gt;=&amp;gt;그때문에&amp;nbsp;DB는&amp;nbsp;Application에서&amp;nbsp;받는게&amp;nbsp;좋음 &lt;br /&gt;=&amp;gt;받는&amp;nbsp;방법은&amp;nbsp;Application&amp;nbsp;class를&amp;nbsp;만들어서&amp;nbsp;그&amp;nbsp;안에&amp;nbsp;public인&amp;nbsp;applicationContext변수를&amp;nbsp;선언해둔다. &lt;br /&gt;=넣을때는&amp;nbsp;requireContext()?.applicationContext&amp;nbsp;as&amp;nbsp;~applicationContext &lt;br /&gt;&lt;br /&gt;-entity는&amp;nbsp;유니크&amp;nbsp;아이디가&amp;nbsp;필요 &lt;br /&gt;=&amp;gt;유니크한&amp;nbsp;프로퍼티위에&amp;nbsp;어노테이션을&amp;nbsp;primaryKey를&amp;nbsp;넣으면&amp;nbsp;됨.&amp;nbsp;없으면&amp;nbsp;autogenerate=true로 &lt;br /&gt;&lt;br /&gt;-Room은&amp;nbsp;Date타입이&amp;nbsp;적용이&amp;nbsp;안되므로&amp;nbsp;Long타입으로&amp;nbsp;바꿔주는&amp;nbsp;converter를&amp;nbsp;추가해야함&amp;nbsp;=&amp;gt;데이터베이스에&amp;nbsp;@TypeConverters로&amp;nbsp;적용 &lt;br /&gt;&lt;br /&gt;-DB도&amp;nbsp;코루틴에서&amp;nbsp;사용할거면&amp;nbsp;Dao에서&amp;nbsp;쿼리&amp;nbsp;메소드를&amp;nbsp;suspend로&amp;nbsp;만들기 &lt;br /&gt;&lt;br /&gt;-데이터베이스&amp;nbsp;객체를&amp;nbsp;생성하고&amp;nbsp;쿼리&amp;nbsp;쓰는건&amp;nbsp;코루틴에서&amp;nbsp;하기 &lt;br /&gt;&lt;br /&gt;@viewModel은&amp;nbsp;View의&amp;nbsp;context를&amp;nbsp;가지면&amp;nbsp;안됨&amp;nbsp;=&amp;gt;둘은&amp;nbsp;라이프사이클이&amp;nbsp;다르므로&amp;nbsp;메모리누수가&amp;nbsp;발생.&amp;nbsp; &lt;br /&gt;=&amp;gt;필요하면&amp;nbsp;어플리케이션&amp;nbsp;컨텍스트를&amp;nbsp;제공 &lt;br /&gt;&lt;br /&gt;@유의점:Adapter역시도 뷰이기 때문에 비즈니스 로직을 뷰모델이나 유스케이스로 최대한 빼줘야 한다. &lt;br /&gt;&lt;br /&gt;@viewModels&amp;lt;뷰모델타입&amp;gt;&amp;nbsp;과&amp;nbsp;activityViewModels의&amp;nbsp;차이점 &lt;br /&gt;=&amp;gt;viewModels는&amp;nbsp;해당&amp;nbsp;viewModel을&amp;nbsp;초기화&amp;nbsp;하는&amp;nbsp;Component의&amp;nbsp;라이프사이클을&amp;nbsp;따름 &lt;br /&gt;=&amp;gt;ActivityViewModels는&amp;nbsp;root&amp;nbsp;Activity의&amp;nbsp;LifeCycle을&amp;nbsp;따름 &lt;br /&gt;=&amp;gt;즉,&amp;nbsp;위에&amp;nbsp;것은&amp;nbsp;component가&amp;nbsp;프래그먼트먼&amp;nbsp;오너가&amp;nbsp;프래그먼트가&amp;nbsp;되어&amp;nbsp;해당&amp;nbsp;라이프사이클을&amp;nbsp;따름.&amp;nbsp; &lt;br /&gt;=&amp;gt;오너가&amp;nbsp;같은&amp;nbsp;동일타입&amp;nbsp;뷰모델은&amp;nbsp;한&amp;nbsp;인스턴스가&amp;nbsp;공유되는&amp;nbsp;상태이다. &lt;br /&gt;&lt;br /&gt;@인터페이스를&amp;nbsp;이용하여&amp;nbsp;데이터&amp;nbsp;동기화 &lt;br /&gt;1) 데이터 변경할 인터페이스 생성 &lt;br /&gt;2) 컨테이너 역할을 하는 액티비티가 인터페이스 상속후 메소드 오버라이드. &lt;br /&gt;=&amp;gt;첫번째&amp;nbsp;프레그먼트가&amp;nbsp;컨텍스트를&amp;nbsp;인터페이스로&amp;nbsp;타입변환하여&amp;nbsp;받은뒤에&amp;nbsp;해당&amp;nbsp;메소드로&amp;nbsp;값&amp;nbsp;변경 &lt;br /&gt;=&amp;gt;바뀐데이터를&amp;nbsp;액티비티가&amp;nbsp;저장 &lt;br /&gt;3) 두번째 프래그먼트 생성시 바뀐 데이터를 건네줌&lt;/p&gt;
&lt;pre id=&quot;code_1723200561603&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface LikeUserEvent{
	fun like(item:User)
}


class MainActivity: AppCompatActivity(), LikeUserEvent{
	private var favoerite:User? = null
    
    override fun onCreate(saveInstanceState:Bundle?){
    	.
        .
        .
        suppporFragmentManager.beginTransaction().
        	replace(
            	R.id.container,
                secondFragment.newInstance(favorite)
            )
            .commit()
    .
    .
    .
    override fun like(item:User){
    	favorite = item
    }
}


class FirstFragment: Fragment(){
	
    private var likeUserEvent : LikeUserEvent? = null
    
    override fun onAttach(context:Context){
    	super.onAttach(context)
        likeUserEvent = context as LikeUserEvent
    }
    
    override fun onViewCreated(view:View, savedInstanceState: Bundle?){
    	//likeuser메소드 활용 코드
    }
    .
    .
    .
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img id=&quot;image-hover-icon&quot; style=&quot;position: absolute; width: 25px; height: 25px; cursor: pointer; left: 158.5px; top: 738.2px; display: none; opacity: 0.7;&quot; src=&quot;chrome-extension://pbhpcbdjngblklnibanbkgkogjmbjeoe/src/public/images/128px.png&quot; /&gt;&lt;/p&gt;</description>
      <category>코틀린-안드로이드</category>
      <author>songyooho</author>
      <guid isPermaLink="true">https://appdevelopjava.tistory.com/93</guid>
      <comments>https://appdevelopjava.tistory.com/93#entry93comment</comments>
      <pubDate>Fri, 9 Aug 2024 21:20:38 +0900</pubDate>
    </item>
    <item>
      <title>67일차)알고리즘 문제(가장 먼 노드, 순위), 개인과제(이미지 검색앱 만들기)</title>
      <link>https://appdevelopjava.tistory.com/92</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;gt;알고리즘 문제&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 가장 먼 노드&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1)문제&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제 설명&lt;/p&gt;
&lt;div style=&quot;background-color: #263747; color: #b2c0cc; text-align: left;&quot;&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;n개의 노드가 있는 그래프가 있습니다. 각 노드는 1부터 n까지 번호가 적혀있습니다. 1번 노드에서 가장 멀리 떨어진 노드의 갯수를 구하려고 합니다. 가장 멀리 떨어진 노드란 최단경로로 이동했을 때 간선의 개수가 가장 많은 노드들을 의미합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;노드의 개수 n, 간선에 대한 정보가 담긴 2차원 배열 vertex가 매개변수로 주어질 때, 1번 노드로부터 가장 멀리 떨어진 노드가 몇 개인지를 return 하도록 solution 함수를 작성해주세요.&lt;/span&gt;&lt;/p&gt;
&lt;span style=&quot;color: #ffffff;&quot;&gt;제한사항&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc; color: #000000;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;노드의 개수 n은 2 이상 20,000 이하입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;간선은 양방향이며 총 1개 이상 50,000개 이하의 간선이 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;vertex 배열 각 행 [a, b]는 a번 노드와 b번 노드 사이에 간선이 있다는 의미입니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2)솔루션&lt;/p&gt;
&lt;pre id=&quot;code_1723116012591&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Solution {
    fun solution(n: Int, edge: Array&amp;lt;IntArray&amp;gt;): Int {
        
        val arr=Array(n){ArrayList&amp;lt;Int&amp;gt;()}
        
        for(i in edge){
            arr[i[0]-1]+=i[1]-1
            arr[i[1]-1]+=i[0]-1
        }
        
        val visited=BooleanArray(n){false}
        val q=ArrayDeque&amp;lt;Pair&amp;lt;Int,Int&amp;gt;&amp;gt;()
        q.addLast(Pair(0,0))
        visited[0]=true
        
        var max=0
        var cnt=0
        while(q.isNotEmpty()){
            val (idx,len) = q.removeFirst()
            
            var isLeaf=true
            for(i in arr[idx]){
                if(!visited[i]){
                    q.addLast(Pair(i,len+1))
                    visited[i]=true
                    isLeaf=false
                }
            }
            
            if(isLeaf){
                if(len==max){
                    cnt++
                }else if(len&amp;gt;max){
                    cnt=1
                    max=len
                }
            }
        }
        
        return cnt
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-BFS로 풀면서 Leaf노드를 체크해서 결과를 찾으면 됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 순위&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1)문제&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제 설명&lt;/p&gt;
&lt;div style=&quot;background-color: #263747; color: #b2c0cc; text-align: left;&quot;&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;n명의 권투선수가 권투 대회에 참여했고 각각 1번부터 n번까지 번호를 받았습니다. 권투 경기는 1대1 방식으로 진행이 되고, 만약 A 선수가 B 선수보다 실력이 좋다면 A 선수는 B 선수를 항상 이깁니다. 심판은 주어진 경기 결과를 가지고 선수들의 순위를 매기려 합니다. 하지만 몇몇 경기 결과를 분실하여 정확하게 순위를 매길 수 없습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;선수의 수 n, 경기 결과를 담은 2차원 배열 results가 매개변수로 주어질 때 정확하게 순위를 매길 수 있는 선수의 수를 return 하도록 solution 함수를 작성해주세요.&lt;/span&gt;&lt;/p&gt;
&lt;span style=&quot;color: #ffffff;&quot;&gt;제한사항&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc; color: #000000;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;선수의 수는 1명 이상 100명 이하입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;경기 결과는 1개 이상 4,500개 이하입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;results 배열 각 행 [A, B]는 A 선수가 B 선수를 이겼다는 의미입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;모든 경기 결과에는 모순이 없습니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2)솔루션&lt;/p&gt;
&lt;pre id=&quot;code_1723116105537&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Solution {
    fun solution(n: Int, results: Array&amp;lt;IntArray&amp;gt;): Int {
        var answer = 0
        //결과 기준으로 그래프 생성
        //이후 인접노드들 전부 갱신
        //A상대로 이겼으면 A가 이긴 상대에게도 전부 이김 =&amp;gt;head갱신
        //A상대로 졌으면 A가 진 상대에게도 전부 짐 =&amp;gt;tail갱신
        //head+tail size가 n이면 등수가 나옴
        
        val nodes=Array&amp;lt;Node&amp;gt;(n+1){Node(it)}
        
        for(i in results){
            nodes[i[0]].tail+=nodes[i[1]]
            nodes[i[1]].head+=nodes[i[0]]
        }
        
        for(i in nodes){
            for(j in i.head){
                j.tail.addAll(i.tail)
            }
            
            for(j in i.tail){
                j.head.addAll(i.head)
            }
        }
        
        for(i in nodes){
            if(i.head.size + i.tail.size == n-1) answer++
        }
        
        
        
        return answer
    }
}

class Node(val n:Int){
    val head=HashSet&amp;lt;Node&amp;gt;()
    val tail=HashSet&amp;lt;Node&amp;gt;()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-주석참고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;gt;개인과제&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://appdevelopjava.tistory.com/91&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://appdevelopjava.tistory.com/91&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1723118402826&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;이미지 검색앱 만들기&quot; data-og-description=&quot;1. 설계:MVVM,Repository,Usecase,UiState Pattern, hilt사용-hilt에 사용하기 위해 ImageSearchApplication을 생성.-MainActivity에 검색결과 프래그먼트(SearchFragment)와 내 보관함 프래그먼트(MyStorageFragment)를 붙이고 각각&quot; data-og-host=&quot;appdevelopjava.tistory.com&quot; data-og-source-url=&quot;https://appdevelopjava.tistory.com/91&quot; data-og-url=&quot;https://appdevelopjava.tistory.com/91&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/4Ir6B/hyWKFkJWEq/E76beEQT2upQuqGXBKeEa0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/cJGfSz/hyWKJ8t2ap/8DcWELbN9BhOTMPgUXuE8k/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/eVFTE/hyWKD8h58U/ymc9tYyK82hKl7xyh5nk81/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400&quot;&gt;&lt;a href=&quot;https://appdevelopjava.tistory.com/91&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://appdevelopjava.tistory.com/91&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/4Ir6B/hyWKFkJWEq/E76beEQT2upQuqGXBKeEa0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/cJGfSz/hyWKJ8t2ap/8DcWELbN9BhOTMPgUXuE8k/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/eVFTE/hyWKD8h58U/ymc9tYyK82hKl7xyh5nk81/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;이미지 검색앱 만들기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;1. 설계:MVVM,Repository,Usecase,UiState Pattern, hilt사용-hilt에 사용하기 위해 ImageSearchApplication을 생성.-MainActivity에 검색결과 프래그먼트(SearchFragment)와 내 보관함 프래그먼트(MyStorageFragment)를 붙이고 각각&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;appdevelopjava.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>코틀린-안드로이드</category>
      <author>songyooho</author>
      <guid isPermaLink="true">https://appdevelopjava.tistory.com/92</guid>
      <comments>https://appdevelopjava.tistory.com/92#entry92comment</comments>
      <pubDate>Thu, 8 Aug 2024 21:00:12 +0900</pubDate>
    </item>
    <item>
      <title>이미지 검색앱 만들기</title>
      <link>https://appdevelopjava.tistory.com/91</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;1. 설계:MVVM,Repository,Usecase,UiState Pattern, hilt사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-hilt에 사용하기 위해 ImageSearchApplication을 생성.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-MainActivity에 검색결과 프래그먼트(SearchFragment)와 내 보관함 프래그먼트(MyStorageFragment)를 붙이고 각각 검색결과와 보관한 이미지가 보이도록함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-RecyclerView를 이용하여 이미지를 화면에 띄움&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-검색어 저장, 검색어 불어오기, 이미지저장, 이미지불러오기, 이미지 삭제를 Usecase로 만들어 구현&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-로컬데이터와 리모트데이터에 대한 레포지토리를 각각 생성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-로컬데이터 소스는 SharedPreference, 리모트데이터 소스는 Retrofit으로 카카오 검색 API를 활용함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 코드&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/wjsghk7664/ImageSearch&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/wjsghk7664/ImageSearch&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1723118155977&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - wjsghk7664/ImageSearch&quot; data-og-description=&quot;Contribute to wjsghk7664/ImageSearch development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/wjsghk7664/ImageSearch&quot; data-og-url=&quot;https://github.com/wjsghk7664/ImageSearch&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/2Km3K/hyWKwuAbOe/lQKIXZAv82zVtUyx47o0t0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/wjsghk7664/ImageSearch&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/wjsghk7664/ImageSearch&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/2Km3K/hyWKwuAbOe/lQKIXZAv82zVtUyx47o0t0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - wjsghk7664/ImageSearch&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Contribute to wjsghk7664/ImageSearch development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 트러블슈팅&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1)hilt사용시 gradle문제&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;=&amp;gt;튜터님의 도움을 받아 gradle설정 완료&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2)예외 처리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;=&amp;gt;Result클래스와 UiState패턴을 활용하여 예외처리를 함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3)hilt Scope설정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;=&amp;gt;스코프는 단일인스턴스가 필요한 것들중 종속관계상의 가장 상위의 것(여기서는 Usecase)만 스코프 설정을 하여 메모리관리와 스코프설정의 목적을 모두 부합하도록 함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>코틀린 개인 과제</category>
      <author>songyooho</author>
      <guid isPermaLink="true">https://appdevelopjava.tistory.com/91</guid>
      <comments>https://appdevelopjava.tistory.com/91#entry91comment</comments>
      <pubDate>Thu, 8 Aug 2024 20:59:49 +0900</pubDate>
    </item>
    <item>
      <title>66일차)알고리즘 문제(여행경로, 입국심사), Compose수업(Recomposition, State, Hoisting), Hilt Scope랑 InstallIn사용시 유의점</title>
      <link>https://appdevelopjava.tistory.com/90</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;gt;알고리즘 문제&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 여행경로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1)문제&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제 설명&lt;/p&gt;
&lt;div style=&quot;background-color: #263747; color: #b2c0cc; text-align: left;&quot;&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;주어진 항공권을 모두 이용하여 여행경로를 짜려고 합니다. 항상 &quot;ICN&quot; 공항에서 출발합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;항공권 정보가 담긴 2차원 배열 tickets가 매개변수로 주어질 때, 방문하는 공항 경로를 배열에 담아 return 하도록 solution 함수를 작성해주세요.&lt;/span&gt;&lt;/p&gt;
&lt;span style=&quot;color: #ffffff;&quot;&gt;제한사항&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc; color: #000000;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;모든 공항은 알파벳 대문자 3글자로 이루어집니다.&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;주어진 공항 수는 3개 이상 10,000개 이하입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;tickets의 각 행 [a, b]는 a 공항에서 b 공항으로 가는 항공권이 있다는 의미입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;주어진 항공권은 모두 사용해야 합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;만일 가능한 경로가 2개 이상일 경우 알파벳 순서가 앞서는 경로를 return 합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;모든 도시를 방문할 수 없는 경우는 주어지지 않습니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2)솔루션&lt;/p&gt;
&lt;pre id=&quot;code_1723029528593&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Solution {
    
    data class Node(val route:Array&amp;lt;String&amp;gt;, val used:BooleanArray)
    
    fun solution(tickets: Array&amp;lt;Array&amp;lt;String&amp;gt;&amp;gt;): Array&amp;lt;String&amp;gt; {
        var answer = arrayOf&amp;lt;String&amp;gt;()
        
        val q=ArrayDeque&amp;lt;Node&amp;gt;()
        
        q.addLast(Node(arrayOf(&quot;ICN&quot;),BooleanArray(tickets.size){false}))
        
        while(q.isNotEmpty()){
            val cur = q.removeFirst()
            
            if(!cur.used.contains(false)){
                answer = if(answer.isEmpty()) cur.route else compares(answer,cur.route)
                continue
            }
            
            for(i in tickets.indices){
                if(!cur.used[i]&amp;amp;&amp;amp;tickets[i][0]==cur.route.last()){
                    val new=Node(cur.route+tickets[i][1],cur.used.copyOf().also{it[i]=true})
                    q.addLast(new)
                }
            }
        }
        
        
        
        return answer
    }
    
    fun compares(str1:Array&amp;lt;String&amp;gt;,str2:Array&amp;lt;String&amp;gt;):Array&amp;lt;String&amp;gt;{
        for(i in str1.indices){
            val tmp = str1[i].compareTo(str2[i])
            if(tmp!=0){
                return if(tmp&amp;lt;0) str1 else str2
            }
        }
        return str1
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-노드에 여행 경로와 사용 티켓정보를 저장&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-BFS로 구하여 결과를 찾는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 입국심사&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1)문제&lt;/p&gt;
&lt;p style=&quot;background-color: #263747; color: #b2c0cc; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;n명이 입국심사를 위해 줄을 서서 기다리고 있습니다. 각 입국심사대에 있는 심사관마다 심사하는데 걸리는 시간은 다릅니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #263747; color: #b2c0cc; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;처음에 모든 심사대는 비어있습니다. 한 심사대에서는 동시에 한 명만 심사를 할 수 있습니다. 가장 앞에 서 있는 사람은 비어 있는 심사대로 가서 심사를 받을 수 있습니다. 하지만 더 빨리 끝나는 심사대가 있으면 기다렸다가 그곳으로 가서 심사를 받을 수도 있습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #263747; color: #b2c0cc; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;모든 사람이 심사를 받는데 걸리는 시간을 최소로 하고 싶습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #263747; color: #b2c0cc; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;입국심사를 기다리는 사람 수 n, 각 심사관이 한 명을 심사하는데 걸리는 시간이 담긴 배열 times가 매개변수로 주어질 때, 모든 사람이 심사를 받는데 걸리는 시간의 최솟값을 return 하도록 solution 함수를 작성해주세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제한사항&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #263747; color: #b2c0cc; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;입국심사를 기다리는 사람은 1명 이상 1,000,000,000명 이하입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;각 심사관이 한 명을 심사하는데 걸리는 시간은 1분 이상 1,000,000,000분 이하입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;심사관은 1명 이상 100,000명 이하입니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2)솔루션&lt;/p&gt;
&lt;pre id=&quot;code_1723029680275&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Solution {
    fun solution(n: Int, times: IntArray): Long {
        var answer: Long = 0
        
        //기준: 각 심사관별 총 심사시간중 최대값이 최소가 되는 것
        //총 심사시간: 인원을 분배하여 심사기간에 곱해준다.
        //최대 시간은 심사관 최대시간*입국심사 기다리는 전체 인원수
        //각 루프별로 체크해서 최대시간을 이진 탐색으로 찾아간다.
        
        times.sort()
        
        var left=0L
        var right=times.last()*n.toLong()
        
        while(left&amp;lt;right){
            val mid=(left+right)/2L
            var total = 0L
            
            //최대값 이하중 최대가 되도록 인원분배
            for(i in times){
                total+=mid/i.toLong()
            }
            
            //총 인원수와 같으면 최대값을 더 내렸을때 총인원수가 같을수 있다
            //예를 들어 [3,5] 11이면 3명 2명으로 총 5명인데 10이면 3명 2명으로 여전히 5명임
            if(total==n.toLong()){
                right=mid
            } 
            //총 인원수가 n보다 작으면 최대값을 늘려야 한다
            else if(total&amp;lt;n.toLong()){
                left=mid+1
            } 
            //총 인원수가 n보다 크면 최대값을 줄여야 한다.
            else{
                right=mid
            }
        }
        
        
        return right
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-심사를 받는데 걸리는 시간, 즉 심사관들의 총 심사시간중 최대값을 이진탐색의 기준으로 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-처음 최대 최소는 가장 오래걸리는 심사관의 심사시간 * 전체 인원수&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-이진탐색을 하며 최대시간을 만족하는 최대 인원을 구해 주어진 인원과 비교하며 이진탐색 구간을 찾아간다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-그렇게 주어진 인원과 같아지면 결과가 나옴&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;gt;Compose수업&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Recomposition&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1)명령형 UI&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-어떻게 UI를 표시하고 업데이트할지 명령 =&amp;gt;UI상태변경을 직접 처리해야함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-setter를 이용해서 직접 업데이트 해줘야함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-예시: 텍스트를 변경할시 setter인 setText()를 이용해서 직접변경을 해줘야 함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2)선언형 UI&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-무엇을 표시하고 싶은지 선언 =&amp;gt;상태가 변하면 UI는 자동으로 업데이트 됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-상태기반으로 UI가 정의되며, 상태 변경시 UI가 자동으로 다시 랜더링됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;=&amp;gt;Recomposition: 상태가 변경된 지점의 Composable함수에 대하여 해당 Composable과 하위 Composable 함수들이 새데이터로 다시 호출되어 그려짐. 단, 해당 상태를 사용하지 않는 함수들은 재구성되지 않음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Smart recomposition&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1)변경 상태 기반 재구성: 상태 변경시 해당 상태를 사용하는 함수들만 재구성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2)선택적 재구성: 루트Composable함수부터 시작하지 않고 상태 변경 지점부터 시작하여 필요부분만 다시 그림&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;=&amp;gt;전체 UI 재구성을 피함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3)효율적 변경 감지: 상태변경을 감지해 동일상태 재설정이나 변경 X인 경우 재구성을 건너뜀&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4)호출된 함수간의 분리: 상태 변경이 발생하는&amp;nbsp; Composable함수는 독립적으로 재구성되며 다른 composable함수에 영향을 주지 않음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-예시: A안에 B와 C가 있고 A는 상태 t를 가진다 하자. 여기서 B는&amp;nbsp; static component이고 C만 상태t에 영향을 받으면 재구성시 B는 재구성에서 제외된다.&lt;/p&gt;
&lt;pre id=&quot;code_1723009942024&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun Parent(){
	val state = remember {mutableStateOf(0)}
    
    Column{
    	Text(&quot;Static&quot;)
        Child(state.value)
        Button(onClick = {state.value +=1}){
        	Text(&quot;increase&quot;)
        }
    }
}

@Composable
fun Child(value:Int){
	Text(&quot;Value: $value&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Static과 stateless&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-Static은 component를 칭함: 상태를 가지지 않는 component&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-Stateless: 상태를 가지지 않는 Composable 함수를 칭함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;=&amp;gt;Recomposition시 Static은 제외되고 Stateless는 재구성에 포함되긴 하지만 성능에 영향이 적음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. State:하위 composable은 상위 composable에게 event를 보내고, 그 반대로는 state를 보낸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) remember:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;1&amp;gt;특징&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-Composable함수는 remember API를 사용해 메모리에 객체를 저장&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-Recomposition이 일어날때 remember에 저장된 값은 그대로 유지됨.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-remember의 수명은 Composable이 호출한 시점부터 해당 Composable이 Composition에서 삭제될때까지&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;2&amp;gt;선언 방법: 3가지 모두 동일한 방식임&lt;/p&gt;
&lt;pre id=&quot;code_1723010494138&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val name = remember { mutableStateOf(default) }
var name by remember { mutableStateOf(default) } //이걸 추천
val (name, setValue) = remember { mutableStateOf(default) }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@주의점: ArrayList나 mutableListOf같은 변경가능 객체를 상태로 사용하면 Compose에서 관찰이 불가능 하거나 오래되거나 잘못된 데이터가 표시될 수 있음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;=&amp;gt;되도록이면 listOf같은 Immutable한 객체를 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@MutableStateOf의 원리: 관찰가능한 MutableState&amp;lt;T&amp;gt;를 생성해서 사용&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot; style=&quot;color: #000000; text-align: left;&quot;&gt;&lt;code&gt;interface MutableState&amp;lt;T&amp;gt; : State&amp;lt;T&amp;gt; {
&amp;nbsp; &amp;nbsp; override var value: T
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2)Flow&lt;/p&gt;
&lt;pre id=&quot;code_1723010940603&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val state by viewModel.viewState.collectAsStateWithLifecycle()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3)LiveData&lt;/p&gt;
&lt;pre id=&quot;code_1723010962411&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val state by viewModel.observeAsState()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. State Hoisting(상태 끌어올림)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1)UI State는 해당 UI State를 읽고 쓰는 모든 Composable의 공통 상위 요소로 Hoisting해야 함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2)재사용성 증가, 테스트에 유용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3)구현 방식: 람다 함수로 인자를 끌어올리도록 만들음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-예시&lt;/p&gt;
&lt;pre id=&quot;code_1723011826121&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
private fun Parent(){
	var name by remember{ mutableStateOf(&quot;&quot;)}
    
    Child(
    	name = name,
        onValueChange = {
        	name = it
        },
    )
}

@Composable
private fun Child(
	name: String,
    onValueChange: ((String) -&amp;gt; Unit),
){
	OutlinedTextField(
    	value =name
        onValueChange = {
        	onValueChange(it)
        }
    )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-OutlinedTextField에서 타자를 치면 람타함수를 통해 Parent로 값이 끌어 올려진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4)구조 예시&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Untitled (16).png&quot; data-origin-width=&quot;751&quot; data-origin-height=&quot;1064&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VYRnh/btsIW7oE3B8/943jkBn99OGwWI2Bg5QJhK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VYRnh/btsIW7oE3B8/943jkBn99OGwWI2Bg5QJhK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VYRnh/btsIW7oE3B8/943jkBn99OGwWI2Bg5QJhK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVYRnh%2FbtsIW7oE3B8%2F943jkBn99OGwWI2Bg5QJhK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;565&quot; height=&quot;800&quot; data-filename=&quot;Untitled (16).png&quot; data-origin-width=&quot;751&quot; data-origin-height=&quot;1064&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;gt;Hilt&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Scope: 스코프를 사용하면 해당 스코프동안 인스턴스가 유지.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;=&amp;gt;Usecase처럼 한번 생성하면 한 인스턴스로만 유지되면 되는 것들중 최상위에 위치한 부분만 Scope를 사용하고 그 아래에는 Scope 사용x&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;=&amp;gt;왜냐하면 어차피 Usecase가 한번 생성되면 그 이후 더이상 생성될일이 없기 때문에 그아래의 종속관계에 있는 객체들은 자동으로 더이상 생성되지 않음. 그때문에 그 이상의 Scope설정은 필요 없음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;=&amp;gt;걔다가 Scope는 메모리 문제로 사용을 필요할때 이외엔 지양해야 하므로 안써도 되면 안쓰는게 좋음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Singleton&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;:만약&amp;nbsp;서로다른&amp;nbsp;뷰모델에서&amp;nbsp;사용해야&amp;nbsp;하거나&amp;nbsp;뷰모델의&amp;nbsp;오너가&amp;nbsp;달라지는&amp;nbsp;경우에서&amp;nbsp;공통된&amp;nbsp;인스턴스를&amp;nbsp;사용해야&amp;nbsp;하면&amp;nbsp;Singleton으로&amp;nbsp;하기&lt;/p&gt;
&lt;p&gt;&lt;img id=&quot;image-hover-icon&quot; style=&quot;position: absolute; width: 25px; height: 25px; cursor: pointer; left: 160.5px; top: 6426.09px; display: none; opacity: 0.7;&quot; src=&quot;chrome-extension://pbhpcbdjngblklnibanbkgkogjmbjeoe/src/public/images/128px.png&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>코틀린-안드로이드</category>
      <author>songyooho</author>
      <guid isPermaLink="true">https://appdevelopjava.tistory.com/90</guid>
      <comments>https://appdevelopjava.tistory.com/90#entry90comment</comments>
      <pubDate>Wed, 7 Aug 2024 20:33:51 +0900</pubDate>
    </item>
    <item>
      <title>65일차)알고리즘 문제(네트워크, 단어 변환), Hilt, 특강정리( 스레드 vs 프로세스, (Android) Main Thread vs Worker Thread- 동기(Synchronous) vs 비동기(Asynchronous), 코루틴 vs 스레드, concurrency vs parallelism, routine vs sub routine vs coroutine,코루틴 빌더,코루틴 스코프,Suspend function,코루틴 디스패쳐,Supervis</title>
      <link>https://appdevelopjava.tistory.com/89</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;gt;알고리즘 문제&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 네트워크&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1)문제&lt;/p&gt;
&lt;p style=&quot;background-color: #263747; color: #b2c0cc; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;네트워크란 컴퓨터 상호 간에 정보를 교환할 수 있도록 연결된 형태를 의미합니다. 예를 들어, 컴퓨터 A와 컴퓨터 B가 직접적으로 연결되어있고, 컴퓨터 B와 컴퓨터 C가 직접적으로 연결되어 있을 때 컴퓨터 A와 컴퓨터 C도 간접적으로 연결되어 정보를 교환할 수 있습니다. 따라서 컴퓨터 A, B, C는 모두 같은 네트워크 상에 있다고 할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #263747; color: #b2c0cc; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;컴퓨터의 개수 n, 연결에 대한 정보가 담긴 2차원 배열 computers가 매개변수로 주어질 때, 네트워크의 개수를 return 하도록 solution 함수를 작성하시오.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제한사항&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #263747; color: #b2c0cc; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;컴퓨터의 개수 n은 1 이상 200 이하인 자연수입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;각 컴퓨터는 0부터&amp;nbsp;n-1인 정수로 표현합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;i번 컴퓨터와 j번 컴퓨터가 연결되어 있으면 computers[i][j]를 1로 표현합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;computer[i][i]는 항상 1입니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2)솔루션&lt;/p&gt;
&lt;pre id=&quot;code_1722946302990&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Solution {
    fun solution(n: Int, computers: Array&amp;lt;IntArray&amp;gt;): Int {
        var answer = 0
        
        val q=ArrayDeque&amp;lt;Int&amp;gt;()
        val visited=BooleanArray(n){false}
        
        q.addLast(0)
        visited[0]=true
        answer++
        
        while(q.isNotEmpty()){
            val cur=q.removeFirst()
            
            for(i in 0..n-1){
                if(!visited[i]&amp;amp;&amp;amp;computers[cur][i]==1){
                    q.addLast(i)
                    visited[i]=true
                }
            }
            if(q.isEmpty()){
                for(i in 0..n-1){
                    if(!visited[i]){
                        q.addLast(i)
                        visited[i]=true
                        println(i)
                        answer++
                        break
                    }
                }
            }
        }
        
        
        return answer
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-BFS를 사용하여 탐색후 더이상 탐색할게 없으면 방문안한 노드에서 하나 더 꺼내오며 answer++하는 방식으로 찾음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 단어 변환&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1)문제&lt;/p&gt;
&lt;p style=&quot;background-color: #263747; color: #b2c0cc; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;두 개의 단어 begin, target과 단어의 집합 words가 있습니다. 아래와 같은 규칙을 이용하여 begin에서 target으로 변환하는 가장 짧은 변환 과정을 찾으려고 합니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #263747; color: #b2c0cc; text-align: left;&quot;&gt;
&lt;pre class=&quot;angelscript&quot; style=&quot;background-color: #202b3d; color: #b2c0cc;&quot;&gt;&lt;code&gt;1. 한 번에 한 개의 알파벳만 바꿀 수 있습니다.
2. words에 있는 단어로만 변환할 수 있습니다.
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;background-color: #263747; color: #b2c0cc; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 begin이 &quot;hit&quot;, target가 &quot;cog&quot;, words가 [&quot;hot&quot;,&quot;dot&quot;,&quot;dog&quot;,&quot;lot&quot;,&quot;log&quot;,&quot;cog&quot;]라면 &quot;hit&quot; -&amp;gt; &quot;hot&quot; -&amp;gt; &quot;dot&quot; -&amp;gt; &quot;dog&quot; -&amp;gt; &quot;cog&quot;와 같이 4단계를 거쳐 변환할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #263747; color: #b2c0cc; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;두 개의 단어 begin, target과 단어의 집합 words가 매개변수로 주어질 때, 최소 몇 단계의 과정을 거쳐 begin을 target으로 변환할 수 있는지 return 하도록 solution 함수를 작성해주세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제한사항&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #263747; color: #b2c0cc; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;각 단어는 알파벳 소문자로만 이루어져 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;각 단어의 길이는 3 이상 10 이하이며 모든 단어의 길이는 같습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;words에는 3개 이상 50개 이하의 단어가 있으며 중복되는 단어는 없습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;begin과 target은 같지 않습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;변환할 수 없는 경우에는 0를 return 합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2)솔루션&lt;/p&gt;
&lt;pre id=&quot;code_1722946398199&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Solution {
    fun solution(begin: String, target: String, words: Array&amp;lt;String&amp;gt;): Int {
        var answer = 0
        
        if(!words.contains(target)) return 0
        
        val arr=Array(words.size){IntArray(words.size)}
        
        for((i,v1) in words.withIndex()){
            for((j,v2) in words.withIndex()){
                if(checkWord(v1,v2)){
                    arr[i][j]=1
                    arr[j][i]=1
                }
            }
        }
        
        
        val q=ArrayDeque&amp;lt;Pair&amp;lt;Int,Int&amp;gt;&amp;gt;() //변환수, 인덱스
        
        for((i,v) in words.withIndex()){
            if(checkWord(begin,v)) {
                q.addLast(Pair(1,i))
            }
        }
        
        while(q.isNotEmpty()){
            val (num,cur) = q.removeFirst()
            
            if(num&amp;gt;words.size) continue
            
            if(target==words[cur]) return num
            
            for(i in words.indices){
                if(arr[cur][i]==1){
                    q.addLast(Pair(num+1,i))
                }
            }
        }
        return 0
    }
    
    fun checkWord(str1:String, str2:String):Boolean{
        var dif=0
        for(i in str1.indices){
            if(str1[i]!=str2[i]) ++dif
        }
        return if(dif==1) true else false
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-우선 단어간 변환 가능한 관계를 표현하는 행렬을 작성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-BFS를 이용하여 구함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;gt;Hilt&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. gradle설정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1)libs.versions.toml&lt;/p&gt;
&lt;pre id=&quot;code_1722917508979&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[versions]
hilt = &quot;2.48&quot;
ksp = &quot;1.9.0-1.0.13&quot;

[libraries]
hilt = { group = &quot;com.google.dagger&quot;, name = &quot;hilt-android&quot;, version.ref = &quot;hilt&quot; }
hilt-compiler = { group = &quot;com.google.dagger&quot;, name = &quot;hilt-android-compiler&quot;, version.ref = &quot;hilt&quot; }

[plugins]
hilt = { id = &quot;com.google.dagger.hilt.android&quot;, version.ref = &quot;hilt&quot;}
ksp = { id = &quot;com.google.devtools.ksp&quot;, version.ref = &quot;ksp&quot;}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2)mudule수준 gradle&lt;/p&gt;
&lt;pre id=&quot;code_1722917584538&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;plugins{
    alias(libs.plugins.hilt)
    alias(libs.plugins.ksp)
}

dependecies{
    implementation(libs.hilt)
    ksp(libs.hilt.compiler)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3)Project수준 gradle&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;plugins{
    alias(libs.plugins.hilt) apply false
    alias(libs.plugins.ksp) apply false
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Application Class생성 및 AndroidManifest수정&lt;/p&gt;
&lt;pre id=&quot;code_1722932244342&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;application
        android:name=&quot;.ImageSearchApplication&quot;
        .
        .&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1722917648267&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@HiltAndroidApp
class ExampleApplication : Application() { 
	override fun onCreate() {
        super.onCreate()
        .
        .
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-주로 API데이터를 들고오거나 DB초기화에 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-hilt구성요소가 생성되어 Application의 수명주기에 연결되 관련 종속 항목 제공 및 액세스 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;=&amp;gt;최상위 컨테이너 역할&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 종속 항목 삽입 - 결합 생성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1)종속항목 주입을 위한 Annotation&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-@HiltAdroidApp: Application&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-@HiltViewModel: ViewModel&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-@AndroidEntryPoint: Activity, Fragment, View, Service, BroadcastReceiver&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@유의점: 어떤 클래스에 @AndroidEnrtyPoint 주석을 지정시 이 클래스에 종속된 클래스에도 주석 지정이 필요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-예시: A activity에서 B Fragment를 사용중이면 B에 주석지정시 A에도 주석지정이 필요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;=&amp;gt;A는 B에 종속된 상태&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2)종속 항목을 받는 계층 구조&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;1&amp;gt;@AndroidEntryPoint는 각 클래스에 관한 개별 Hilt구성요소 생성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;=&amp;gt;계층구조에 따라 상위클래스에서 종속목을 받을 수 있음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;2&amp;gt;계층구조&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;hilt-hierarchy.svg&quot; data-origin-width=&quot;177&quot; data-origin-height=&quot;150&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0XO6L/btsIWdIirjk/KSYbqdMOD8sAORh4iY07G0/tfile.svg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0XO6L/btsIWdIirjk/KSYbqdMOD8sAORh4iY07G0/tfile.svg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0XO6L/btsIWdIirjk/KSYbqdMOD8sAORh4iY07G0/tfile.svg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0XO6L%2FbtsIWdIirjk%2FKSYbqdMOD8sAORh4iY07G0%2Ftfile.svg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;424&quot; data-filename=&quot;hilt-hierarchy.svg&quot; data-origin-width=&quot;177&quot; data-origin-height=&quot;150&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;img id=&quot;image-hover-icon&quot; style=&quot;position: absolute; width: 25px; height: 25px; cursor: pointer; left: 13px; top: 13625.5px; display: none; opacity: 0.7;&quot; src=&quot;chrome-extension://pbhpcbdjngblklnibanbkgkogjmbjeoe/src/public/images/128px.png&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3)종속 항목 가져오기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-@Inject를 이용하며 필드나 생성자내 프로퍼티에 삽입 실행 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&quot;필드 주입&quot;시에는 @Inject lateinit var 을 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;=&amp;gt;private는 사용 불가능!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&quot;생성자 주입&quot;시에는 class ClassName @Inject constructor(private val ~, val ~,...){}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;=&amp;gt;생성자 파라미터도 주입을 받아야 하면 해당 클래스도 @Inject로 만들어주거나 Module사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@필드주입은 Activity같이 생성자 주입이 불가능한 곳에서만 사용하고 나머진 생성자 주입 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4)Hilt Module사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;1&amp;gt;생성자 주입을 사용할 수 없는 경우 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-인터페이스인 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-외부 라이브러리인 경우(ex-Retrofit, OkHttp, Room같은 빌터 패턴으로 인스턴스를 생성해야 하는 경우)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;2&amp;gt;사용 방법&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-인터페이스의 경우:@Bind사용&lt;/p&gt;
&lt;pre id=&quot;code_1722919690814&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface AnalyticsService {
  fun analyticsMethods()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[1]인터페이스의 구현체에 @Inject로 결합 생성&lt;/p&gt;
&lt;pre id=&quot;code_1722919716575&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class AnalyticsServiceImpl @Inject constructor(
  ...
) : AnalyticsService { ... }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[2]abstract class로 Module생성&lt;/p&gt;
&lt;pre id=&quot;code_1722919752344&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Module
@InstallIn(ActivityComponent::class)
abstract class AnalyticsModule {
  @Binds
  abstract fun bindAnalyticsService(
    analyticsServiceImpl: AnalyticsServiceImpl
  ): AnalyticsService
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;=&amp;gt;특징: 그저 연결만 해주는 것이기 때문에 메소드는 추상 메소드이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;=&amp;gt;묶는법: 파라미터에 구현체, 반환타입에 인터페이스를 쓰면&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-외부라이브러리인 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(1)특징: binds와 같은 방식인데 라이브러리에 따라 그냥 메소드인 경우도 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;=&amp;gt;이런경우 보통object로 하면 됨.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;=&amp;gt;그냥 메소드인경우 반환값까지 설정해서 어떤식으로 주입할값을 만드는지도 명시해놓으면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(1)InstallIn()은 스코프를 의미: 스코프에 따라 사용할 수 있는 범위가 지정되고 수명이 결정 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(2)예시: InstallIn(AcitivityComponent:class)로 지정된 모듈이면 동일 액티비티 내에서 사용될때는 동일 인스턴스를, 서로다른 액티비티에 대해서는 서로다른 인스턴스를 주입한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(3)종류&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-SingletonComponent: 애플리케이션 수명주기 동안 살아있음. 애플리케이션 수준의 의존성 제공시 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-ActivityComponent: 액티비티 수명주기 동안 살아있음. 액티비티 수준의 의존성 제공시 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-FragmentComponent: 프래그먼트 수명주기 동안 살아있음. 프래그먼트 수준의 의존성 제공시 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-ViewComponent: 뷰의&amp;nbsp; 수명주기 동안 살아있음. 뷰 수준 의존성 제공시 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-ViewModelComponent: 뷰 모델 수명주기 동안 살아있음. 뷰 모델 수준 의존성 제공시 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-ServiceComponent: 서비스 수명주기 동안 살아있음. 서비스 스준 의존성 제공시 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(4)사용: Module에만 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Scope지정법: Module내 의존성(@Provides, @Binds)이나 생성자에 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(1)의미: 해당 스코프 내에서 단일 인스턴스를 유지&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;=&amp;gt;스코프를 지정안하면 매번 새로운 인스턴스를 생성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(2)종류&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-@Singleton: 앱 전체에서 단일 인스턴스 유지&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-@ActivityScoped: 각 액티비티 생명주기 동안 단일 인스턴스 유지&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-@FragmentScoped: 각 프래그먼트 생명주기 동안 단일 인스턴스 유지&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-@ViewScoped: 각 뷰의 생명주기 동안 단일 인스턴스 유지&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-@ViewModelScoped: 각 뷰모델 생명주기 동안 단일 인스턴스 유지&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-@ServiceScoped: 각 서비스 생명주기 동안 단일 인스턴스 유지&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(3)유의점: Module에서 사용시 하위컴포넌트에서 사용 불가&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;=&amp;gt;예를들어 ActivityComponent에서 @Singleton은 사용 불가&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@클래스, 구성요소, 범위 정리&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;힐트 스코프 정리.png&quot; data-origin-width=&quot;697&quot; data-origin-height=&quot;496&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/enYzKA/btsIUAriN1G/Iz4ZRp8TQK8CRLk8gGxzOK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/enYzKA/btsIUAriN1G/Iz4ZRp8TQK8CRLk8gGxzOK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/enYzKA/btsIUAriN1G/Iz4ZRp8TQK8CRLk8gGxzOK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FenYzKA%2FbtsIUAriN1G%2FIz4ZRp8TQK8CRLk8gGxzOK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;697&quot; height=&quot;496&quot; data-filename=&quot;힐트 스코프 정리.png&quot; data-origin-width=&quot;697&quot; data-origin-height=&quot;496&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Scope사용 유의점: 제공된 객체는 component가 제거될때까지 메모리에 남아있기때문에 결합에서 scope를 지정하면 cost가 많이 들 수 있음. 그러니 필요한 경우가 아니면 scope 지정된 결합 사용은 지양해야함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;=&amp;gt;특정 scope내에 동일 인스턴스 사용시나 동기화가 필요하거나 생성에 큰 cost가 드는 경우같을때 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5)동일 유형을 여려가지로 제공&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-@Qualifier: 인터페이스의 구현체가 여러종류인 경우 주석을 이용해서 Inject시 명시해줄 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-사용 예시&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;1&amp;gt;구현체에 주석 달기&lt;/p&gt;
&lt;pre class=&quot;less&quot; style=&quot;color: #000000; text-align: left;&quot;&gt;&lt;code&gt;@Qualifier
annotation class InMemoryLogger

@Qualifier
annotation class DatabaseLogger

@InstallIn(ApplicationComponent::class)
@Module
abstract class LoggingDatabaseModule {

&amp;nbsp; &amp;nbsp; @DatabaseLogger
&amp;nbsp; &amp;nbsp; @Singleton
&amp;nbsp; &amp;nbsp; @Binds
&amp;nbsp; &amp;nbsp; abstract fun bindDatabaseLogger(impl: LoggerLocalDataSource): LoggerDataSource
}

@InstallIn(ActivityComponent::class)
@Module
abstract class LoggingInMemoryModule {

&amp;nbsp; &amp;nbsp; @InMemoryLogger
&amp;nbsp; &amp;nbsp; @ActivityScoped
&amp;nbsp; &amp;nbsp; @Binds
&amp;nbsp; &amp;nbsp; abstract fun bindInMemoryLogger(impl: LoggerInMemoryDataSource): LoggerDataSource
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;2&amp;gt;Inject위에 어노테이션 추가&lt;/p&gt;
&lt;pre class=&quot;less&quot; style=&quot;color: #000000; text-align: left;&quot;&gt;&lt;code&gt;@AndroidEntryPoint
class LogsFragment : Fragment() {

&amp;nbsp; &amp;nbsp; @InMemoryLogger
&amp;nbsp; &amp;nbsp; @Inject lateinit var logger: LoggerDataSource
&amp;nbsp; &amp;nbsp; ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6)Context주입&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-Application: @AppicationContext context: Context&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-Activity: @ActivityContext context: Context&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-Fragment: @FragmentContext context: Context&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7)Hilt가 지원하지 않는 종류 클래스에 종속 항목 삽입&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;1&amp;gt;방법:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-원하는 결합유형마다 @EntryPoint 주석이 지정된 인터페이스를 정의후 @InstallIn으로 진입점 설치할 구성요소 지정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-이후 EntryPointAccessors 오 from~을 이용. ~부분은 @InstallIn에 따라 결정. Argument는 구성요소 이거나 구성요소 소유자 역할을 하는 @AndroidEnrtyPoint객체여야 함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;2&amp;gt;예시:ContentProvider는 hilt에서 지원안함&lt;/p&gt;
&lt;pre id=&quot;code_1722923922954&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class ExampleContentProvider : ContentProvider() {

  @EntryPoint
  @InstallIn(SingletonComponent::class)
  interface ExampleContentProviderEntryPoint {
    fun analyticsService(): AnalyticsService
  }

  ...
  
  override fun query(...): Cursor {
    val appContext = context?.applicationContext ?: throw IllegalStateException()
    val hiltEntryPoint =
      EntryPointAccessors.fromApplication(appContext, ExampleContentProviderEntryPoint::class.java)

    val analyticsService = hiltEntryPoint.analyticsService()
    ...
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@@참고 링크@@&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.android.com/training/dependency-injection/hilt-android?hl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developer.android.com/training/dependency-injection/hilt-android?hl=ko&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1722923056372&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Hilt를 사용한 종속 항목 삽입 &amp;nbsp;|&amp;nbsp; Android Developers&quot; data-og-description=&quot;이 페이지는 Cloud Translation API를 통해 번역되었습니다. Hilt를 사용한 종속 항목 삽입 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. Hilt는 프로젝트에서 종속&quot; data-og-host=&quot;developer.android.com&quot; data-og-source-url=&quot;https://developer.android.com/training/dependency-injection/hilt-android?hl=ko&quot; data-og-url=&quot;https://developer.android.com/training/dependency-injection/hilt-android?hl=ko&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/ceH3rL/hyWKvIAEAd/xLI8lcIg76vqSwZO8DmvVK/img.png?width=1201&amp;amp;height=676&amp;amp;face=0_0_1201_676&quot;&gt;&lt;a href=&quot;https://developer.android.com/training/dependency-injection/hilt-android?hl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.android.com/training/dependency-injection/hilt-android?hl=ko&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/ceH3rL/hyWKvIAEAd/xLI8lcIg76vqSwZO8DmvVK/img.png?width=1201&amp;amp;height=676&amp;amp;face=0_0_1201_676');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Hilt를 사용한 종속 항목 삽입 &amp;nbsp;|&amp;nbsp; Android Developers&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;이 페이지는 Cloud Translation API를 통해 번역되었습니다. Hilt를 사용한 종속 항목 삽입 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. Hilt는 프로젝트에서 종속&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.android.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;gt;특강 정리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 스레드 vs 프로세스&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-프로세스: 메모리에 올라와 실행되고 있는 프로그램의 인스턴스(독립적객체)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;=&amp;gt;실행된 프로그램을 의미, 프로세스간 자원접근은 IPC(inter-process communication)을 사용해야 함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-스레드: 프로세스 내에서 실행되는 여러 흐름의 단위&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;=&amp;gt;스레드간에는 stack을 제외한 Code,Data,Heap영역을 공유하므로 힙메모리를 서로 읽고 쓰기 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. (Android)Main Thread vs Worker Thread&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-Main Thread(=UI Thread): UI그리는 작업을 맡는 스레드, 다른 작업도 가능하나 작업 시간이 길어지면 ANR이 날 수 있으므로 되도록이면 지양하는게 좋음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-Worker Thread: UI그리는 작업 외의 네트워크작업이나 긴시간이 걸리는 작업등을 하는 스레드&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 동기(synchronous) vs 비동기(Asynchronous)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-동기: 순차처리. 요청이 들어오면 순차적으로 처리하기때문에 다른 작업들은 완료까지 대기하게 됨.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-비동기: 작업을 독립적으로 실행. 작업이 완료되지 않더라도 다음 작업을 실행함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 코루틴 vs 스레드&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-스레드:&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1)작업 단위:Thread&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2)동시성 보장 수단: Context switching&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-Blocking: Thead A가 Thead B의 결과를 기다려야 하면 Thread A는 Blocking되며 그동안 Thread A는 멈춰있는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-코루틴:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1)작업단위: Coroutine Object&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2)동시성 보장 수단: Programmer Switching&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-프로그래머의 코드를 통해 Switching시점을 자유롭게 정함 =&amp;gt;운영체제 커널에 의해 결정되는 스레드와는 다름&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-Suspend(Non-Blocking): Object1이 Object2의 결과를 기다려야 한다고 Object1이 실행되던 Thread는 멈추지 않고 계속 일을 함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;=&amp;gt;이러한 이유로 한 스레드에서 여러 코루틴작업이 일어날 수 있고 한 코루틴이 여러 스레드를 건너가며 실행될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. Concurrency(동시성) vs Parallelism(병렬성)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-동시성:동시에 여러작업을 수행. 시분할(Interleaving) 기법을 활용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@시분할 기법: 여러작업을 각각 잘개 쪼갠뒤에 번갈아 가며 수행하여 동시에 일어나는것 처럼 보이게 하는것&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-병렬성: 시분할 기법없이 실제로 여러 작업을 동시에 수행&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;=&amp;gt;작업 수행을 위한 자원(cpu core)이 여러개 존재해야 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. routine vs subroutine vs coroutine&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-Routine: 프로그램 일부로써 특정 일을 실행하기 위한 일련의 명령.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;=&amp;gt;여기서 일련의 명령을 일컬어 함수라고 부름&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-SubRoutine: 함수안에 함수가 있는 경우 안쪽에 있는 함수를 서브루틴이라 부름&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-Coroutine: 함께 실행되는 함수&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;=&amp;gt;서로 다른 함수가 서로 Thread점유를 양보하면서 함께 수행되는 것을 코루틴이라 부름&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;=&amp;gt;스레드 자원 활용성이 극대화&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7. 코루틴 빌더&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-코루틴을 만드는 함수&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- runBlocking, launch, async, withContext&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@runBlocking&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1)runBlocking내 코드가 실행될때까지 MainThread를 Blocking함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2)그때문에 안드로이드 메인스레드 호출 환경에서 사용하면 ANR이 발생할수 있음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3)주로 unit test에 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;=&amp;gt;fun main() = runBlocking{} 같은 형태로 블록내에 테스트할 코드를 넣음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;8. 코루틴 스코프&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-GlobalScope:&amp;nbsp;최상위&amp;nbsp;스코프라&amp;nbsp;application이&amp;nbsp;종료될때&amp;nbsp;까지&amp;nbsp;살아있음 &lt;br /&gt;=&amp;gt;되도록&amp;nbsp;사용하지&amp;nbsp;말고&amp;nbsp;AAC&amp;nbsp;워크스테이션쓰기(다시&amp;nbsp;듣기) &lt;br /&gt;-CoroutineScope&amp;nbsp;with&amp;nbsp;Android&amp;nbsp;Components &lt;br /&gt;&amp;lt;1&amp;gt;ViewModelScope:&amp;nbsp;뷰모델&amp;nbsp;생명주기에&amp;nbsp;맞춰&amp;nbsp;코루틴&amp;nbsp;Scope관리 &lt;br /&gt;=&amp;gt;Viewmodel의&amp;nbsp;onCleared()에&amp;nbsp;작업&amp;nbsp;취소 &lt;br /&gt;&amp;lt;2&amp;gt;LifeCycleScope:&amp;nbsp;View의&amp;nbsp;Lifecycle객체에&amp;nbsp;정의&amp;nbsp;되어&amp;nbsp;생명주기&amp;nbsp;끝날시&amp;nbsp;종료됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;9. Suspend function&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Suspend: 언제든지 시작, 멈추고 , 다시시작 가능한 함수. &lt;br /&gt;-이점:&amp;nbsp;Thead는&amp;nbsp;block되면&amp;nbsp;thread는&amp;nbsp;다른&amp;nbsp;작업을&amp;nbsp;할&amp;nbsp;수&amp;nbsp;없는&amp;nbsp;block상태가됨 &lt;br /&gt;=&amp;gt;반면&amp;nbsp;suspend함수는&amp;nbsp;block됐을때&amp;nbsp;그동안&amp;nbsp;thread에서는&amp;nbsp;다른&amp;nbsp;작업&amp;nbsp;수행&amp;nbsp;가능 &lt;br /&gt;=&amp;gt;스레드&amp;nbsp;내에서&amp;nbsp;코루틴을&amp;nbsp;여러개가&amp;nbsp;실행가능하고&amp;nbsp;코루틴은&amp;nbsp;여러&amp;nbsp;스레드에서&amp;nbsp;실행될&amp;nbsp;수&amp;nbsp;있음 &lt;br /&gt;=&amp;gt;&amp;nbsp;suspension&amp;nbsp;point도달시&amp;nbsp;바로&amp;nbsp;다른&amp;nbsp;코루틴&amp;nbsp;실행가능 &lt;br /&gt;=&amp;gt;light&amp;nbsp;weight(경량)하다&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;10. 코루틴 디스패쳐: 코루틴 실행을 스케줄링하기 위해 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;=&amp;gt;코루틴이 사용할 스레드를 제어하는 작업 담당&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;-Dispatcher.IO : I/O를 많이 사용하는 작업&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;=&amp;gt;네트워크/DB 입출력이 있는 작업들에 대해 적절한 Thread로 할당하는 역할. 스레드 풀에서 최대 64개의 Thread를 생성하여 할당이 가능하다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;-Dispatcher.Main : UI와 직접 상호 작용을 하는 작업을 메인 Thread에 전달하는 역할&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;-Dispatcher.Default: 연속적인 작업, CPU를 많이 사용하는 작업 등을 수행할 때 적절한 Thread로 할당하는 역할. 스레드 풀에서 보통 CPU Core의 개수에 해당하는 Thread를 생성하여 할당이 가능하다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;11. Supervisor Job&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1)일반적으로 Job만 사용하면 코루틴에서 에러가 발생시 cancel이 아랫방향, 윗방향 모두 전파되어 전부 취소됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;=&amp;gt;A의 자식 B,C가 있을시 B에서 에러로 취소 발생하면 A로 전파후 다시 A는C로 전파하여 A,B,C전부 취소남&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2)Supervisor Job: 예외에 의한 취소를 아래방향으로만 내려가게 함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-예시&lt;/p&gt;
&lt;pre id=&quot;code_1722945755425&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;suspend fun random1(): Int {
    delay(1000L)
    return Random.nextInt(0, 500)
}

suspend fun random2Exception() {
    delay(500L)
    throw ArithmeticException()
}

fun main() = runBlocking {
    val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
    val job1 = scope.launch { println(random1()) }
    val job2 = scope.launch { random2Exception() }
    joinAll(job1, job2)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-이러면 원래는 r1, r2전부 취소나야 하지만 SupervisorJob덕분에 r1은 정상 작동한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;12. Job&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-사용: 코루틴을 컨트롤하기위해 사용.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-1대 다수: 하나의 코루틴의 동작을 제어하기도 하고 N개의 코루틴 동작을 제어하기도 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-Job의 States&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Untitled (15).png&quot; data-origin-width=&quot;1166&quot; data-origin-height=&quot;445&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkgdyG/btsIXe70ew4/ewRTDWBFhqpbWPFSsk3940/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkgdyG/btsIXe70ew4/ewRTDWBFhqpbWPFSsk3940/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkgdyG/btsIXe70ew4/ewRTDWBFhqpbWPFSsk3940/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkgdyG%2FbtsIXe70ew4%2FewRTDWBFhqpbWPFSsk3940%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1166&quot; height=&quot;445&quot; data-filename=&quot;Untitled (15).png&quot; data-origin-width=&quot;1166&quot; data-origin-height=&quot;445&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-Job으로 할 수 있는것&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1)start: 현재 코루틴 동작을 체크해 동작중이면 true, 아니면 false반환&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2)join: 현재 코루틴 동작이 끝날때까지 대기 =&amp;gt;await처럼 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3)cancel: 현재 코루틴을 즉시 종료하도록 유도만 하고 대기하지는 않음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;=&amp;gt;타이트하게 동작하는 단순루프에서는 delay가 없으면 종료하지 못함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4)cancelAndJoin: 현재 코루틴에 종료하라는 신호를 보내고 정상종료까지 대기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5)cancelChildren: 코루틴 스코프내의 자식 코루틴들을 종료&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;=&amp;gt;cancel 과는 달리 하위 아이템들만 종료하며 부모는 취소 X&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Mapping: A 형태 클래스를 B형태 클래스로 바꿈&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;=&amp;gt;map함수를 이용해서 만들어주면 됨.&lt;/p&gt;</description>
      <category>코틀린-안드로이드</category>
      <author>songyooho</author>
      <guid isPermaLink="true">https://appdevelopjava.tistory.com/89</guid>
      <comments>https://appdevelopjava.tistory.com/89#entry89comment</comments>
      <pubDate>Tue, 6 Aug 2024 21:16:20 +0900</pubDate>
    </item>
  </channel>
</rss>