<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>harudevelop2_.log</title>
        <link>https://velog.io/</link>
        <description>응애...아무것도 모르는 개발자 흉내라도 내고 싶은 비전공자입니다.</description>
        <lastBuildDate>Fri, 16 Feb 2024 11:40:17 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>harudevelop2_.log</title>
            <url>https://velog.velcdn.com/images/harudevelop2_/profile/c75f9a62-901f-4926-8f30-f0557c9a1963/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. harudevelop2_.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/harudevelop2_" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[Android/Kotlin]MVVM 예제로 이해하기(1)]]></title>
            <link>https://velog.io/@harudevelop2_/AndroidKotlinMVVM-%EC%98%88%EC%A0%9C%EB%A1%9C-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B01</link>
            <guid>https://velog.io/@harudevelop2_/AndroidKotlinMVVM-%EC%98%88%EC%A0%9C%EB%A1%9C-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B01</guid>
            <pubDate>Fri, 16 Feb 2024 11:40:17 GMT</pubDate>
            <description><![CDATA[<p>원래였으면 모든걸 액티비티에 몰아 넣어서 구현 했을 기능..
뷰모델 하나 추가하니 더 어려워졌따.!!</p>
<h1 id="뷰모델을-사용하기-위한-gradle-추가">뷰모델을 사용하기 위한 gradle 추가</h1>
<p>모듈 수준의 그래들에 의존성을 추가해준다.</p>
<pre><code>implementation(&quot;androidx.activity:activity-ktx:1.8.2&quot;)
implementation(&quot;androidx.fragment:fragment-ktx:1.6.2&quot;)
implementation (&quot;androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0&quot;)
implementation (&quot;androidx.lifecycle:lifecycle-livedata-ktx:2.4.1&quot;)</code></pre><h2 id="data-class">data class</h2>
<pre><code>data class User(val name : String)</code></pre><p>사용자 이름을 저장할 데이터 클래스를 하나 만들고~</p>
<h2 id="viewmodel">ViewModel</h2>
<p>뷰모델은 뷰와 모델의 중간다리 역할로 LiveData를 등록 시킨다.</p>
<pre><code>class UserViewModel : ViewModel() {
    private val _user = MutableLiveData&lt;User&gt;()
    val user: LiveData&lt;User&gt; get() = _user

    fun setUserName(name: String) {
        _user.value = User(name)
    }
}</code></pre><p>뷰모델을 설정 해주었다.</p>
<h2 id="activity-레이아웃">activity 레이아웃</h2>
<pre><code>&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;LinearLayout xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    xmlns:tools=&quot;http://schemas.android.com/tools&quot;
    android:layout_width=&quot;match_parent&quot;
    android:layout_height=&quot;match_parent&quot;
    android:orientation=&quot;vertical&quot;
    android:padding=&quot;16dp&quot;
    tools:context=&quot;.MainActivity&quot;&gt;

    &lt;EditText
        android:id=&quot;@+id/editTextName&quot;
        android:layout_width=&quot;match_parent&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:hint=&quot;Enter your name&quot; /&gt;

    &lt;Button
        android:id=&quot;@+id/buttonSet&quot;
        android:layout_width=&quot;wrap_content&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:text=&quot;Set Name&quot; /&gt;

    &lt;TextView
        android:id=&quot;@+id/textViewGreeting&quot;
        android:layout_width=&quot;wrap_content&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:layout_marginTop=&quot;16dp&quot;
        android:textSize=&quot;18sp&quot;
        android:textStyle=&quot;bold&quot; /&gt;

&lt;/LinearLayout&gt;</code></pre><h3 id="livedata">LiveData</h3>
<p>Jeckpat 라이브러리에서 제공하는 클래스</p>
<blockquote>
<p>수명 주기 인식: LiveData는 수명 주기를 인식하여 관찰자(Observer)에게 데이터 변경 사항을 알립니다. 이는 메모리 누수를 방지하고 사용자가 활성화된 상태에서만 데이터를 업데이트하는 데 도움이 됩니다.</p>
</blockquote>
<blockquote>
<p>UI 업데이트: LiveData는 UI 컨트롤러(예: 액티비티, 프래그먼트)와 함께 사용되어 UI의 상태를 업데이트하는 데 사용됩니다. 데이터가 변경될 때마다 관찰자에게 자동으로 알림을 보내므로 UI를 업데이트하는 코드를 별도로 작성할 필요가 없습니다.</p>
</blockquote>
<blockquote>
<p>데이터 홀더: LiveData는 데이터를 보유하고 관찰자에게 전달하는 데 사용됩니다. 이는 앱의 데이터를 관리하고, UI와 데이터 간의 결합도를 낮추는 데 도움이 됩니다.</p>
</blockquote>
<blockquote>
<p>반응형 프로그래밍: LiveData는 반응형 프로그래밍 패턴을 따르며, 데이터의 변경에 따라 자동으로 반응하여 UI를 업데이트합니다. 이는 앱의 사용자 경험을 향상시키는 데 도움이 됩니다.</p>
</blockquote>
<h2 id="mainactivity">MainActivity</h2>
<p>LiveData를 관찰하는 observer</p>
<pre><code>class MainActivity : AppCompatActivity() {
    private val viewModel : UserViewModel by viewModels()

    private val binding by lazy {ActivityMainBinding.inflate(layoutInflater)}

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)

        binding.buttonSet.setOnClickListener {
            val name = binding.editTextName.text.toString()
            viewModel.setUserName(name)
        }

        viewModel.user.observe(this, Observer { user -&gt;
            binding.textViewGreeting.text = &quot;Hello ${user.name}&quot;
        })
    }
}</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[Android/Kotlin] kakao api ImageSearch(보완본)]]></title>
            <link>https://velog.io/@harudevelop2_/AndroidKotlin-kakao-api-ImageSearch%EB%B3%B4%EC%99%84%EB%B3%B8</link>
            <guid>https://velog.io/@harudevelop2_/AndroidKotlin-kakao-api-ImageSearch%EB%B3%B4%EC%99%84%EB%B3%B8</guid>
            <pubDate>Fri, 02 Feb 2024 05:57:05 GMT</pubDate>
            <description><![CDATA[<p>다시 처음부터 시작하는 카카오 api 이미지 받아오기
보완 영상에서는 배웠던거랑 다르게 나왔다.?</p>
<h1 id="kakao-api">KaKao api</h1>
<h2 id="constants">Constants</h2>
<p>우선 보완영상에서는 URL, HEADER, Preference key/Name이 Constants로 별도로 관리하였다.</p>
<pre><code>object Constants {

    // Kakao Image Search API의 기본 URL입니다.
    const val BASE_URL = &quot;https://dapi.kakao.com&quot;

    // Kakao API를 사용하기 위한 인증 헤더입니다.
    const val AUTH_HEADER = &quot;KakaoAK {API KEY}&quot;

    // 앱의 Shared Preferences 파일 이름입니다.
    const val PREFS_NAME = &quot;com.jblee.imagesearch.prefs&quot;

    // 마지막 검색어를 저장하기 위한 키 값입니다.
    const val PREF_KEY = &quot;IMAGE_SEARCH_PREF&quot;
}</code></pre><h1 id="retrofit작성하기">Retrofit작성하기</h1>
<pre><code>object retrofit_client {

    // API 서비스 객체를 반환한다.
    val apiService: Retrofit_interface
        get() = instance.create(Retrofit_interface::class.java)

    // Retrofit 인스턴스를 초기화하고 반환한다.
    private val instance: Retrofit
        private get() {
            // Gson 객체 생성. setLenient()는 JSON 파싱이 좀 더 유연하게 처리되도록 한다.
            val gson = GsonBuilder().setLenient().create()

            // Retrofit 빌더를 사용하여 Retrofit 인스턴스 생성
            return Retrofit.Builder()
                .baseUrl(Constants.BASE_URL)  // 기본 URL 설정
                .addConverterFactory(GsonConverterFactory.create(gson))  // JSON 파싱을 위한 컨버터 추가
                .build()
        }
}</code></pre><h2 id="interface">interface</h2>
<pre><code>interface Retrofit_interface {

    @GET(&quot;v2/search/image&quot;)
    fun image_search(
        @Header(&quot;Authorization&quot;) apiKey: String?,
        @Query(&quot;query&quot;) query: String?,
        @Query(&quot;sort&quot;) sort: String?,
        @Query(&quot;page&quot;) page: Int,
        @Query(&quot;size&quot;) size: Int
    ): Call&lt;ImageModel?&gt;?
}</code></pre><h2 id="imagemodel">ImageModel</h2>
<pre><code>data class ImageModel(
    @SerializedName(&quot;documents&quot;)
    val documents: ArrayList&lt;Documents&gt;,

    @SerializedName(&quot;meta&quot;)
    val meta: Meta
) {
    /**
     * 이미지 검색 응답에서 단일 문서 혹은 결과를 나타내는 클래스.
     */
    data class Documents(
        @SerializedName(&quot;collection&quot;)
        val collection: String,

        @SerializedName(&quot;thumbnail_url&quot;)
        val thumbnailUrl: String,

        @SerializedName(&quot;image_url&quot;)
        val imageUrl: String,

        @SerializedName(&quot;width&quot;)
        val width: Int,

        @SerializedName(&quot;height&quot;)
        val height: Int,

        @SerializedName(&quot;display_sitename&quot;)
        val displaySitename: String,

        @SerializedName(&quot;doc_url&quot;)
        val docUrl: String,

        @SerializedName(&quot;datetime&quot;)
        val datetime: String
    )

    /**
     * 이미지 검색 응답에 대한 메타 정보를 나타내는 클래스.
     */
    data class Meta(
        @SerializedName(&quot;is_end&quot;)
        val isEnd: Boolean,

        @SerializedName(&quot;pageable_count&quot;)
        val pageableCount: Int,

        @SerializedName(&quot;total_count&quot;)
        val totalCount: Int
    )
}
</code></pre><h1 id="utils">Utils</h1>
<blockquote>
<h2 id="api에서-받아온-시간을-원하는-표시형식으로-바꾸기-위한-코드">api에서 받아온 시간을 원하는 표시형식으로 바꾸기 위한 코드</h2>
</blockquote>
<pre><code>    fun getDateFromTimestampWithFormat(
        timestamp: String?,
        fromFormatformat: String?,
        toFormatformat: String?
    ): String {
        var date: Date? = null
        var res = &quot;&quot;
        try {
            val format = SimpleDateFormat(fromFormatformat)
            date = format.parse(timestamp)
        } catch (e: ParseException) {
            e.printStackTrace()
        }
        val df = SimpleDateFormat(toFormatformat)
        res = df.format(date)
        return res
    }</code></pre><blockquote>
<h2 id="preference--마지막-검색어를-저장하기-위한-코드">preference : 마지막 검색어를 저장하기 위한 코드.</h2>
</blockquote>
<pre><code>fun saveLastSearch(context: Context, query: String) {
        val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
        prefs.edit().putString(PREF_KEY, query).apply()
    }
fun getLastSearch(context: Context): String? {
        val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
        return prefs.getString(PREF_KEY, null)
    }</code></pre><h1 id="searchitemmodel">SearchItemModel</h1>
<pre><code>data class SearchItemModel(
    var title: String,
    var dateTime: String,
    var url: String,
    var isLike: Boolean = false
)</code></pre><h1 id="mainactivity">MainActivity</h1>
<blockquote>
<h2 id="공유저장소">공유저장소</h2>
</blockquote>
<pre><code>var likedItems : ArrayList&lt;SearchItemModel&gt; = ArrayList()</code></pre><p>나중에 클릭된 아이템들이 likedItems에 들어갈 수 있도록 만듬.</p>
<blockquote>
<h2 id="addlikeditem">addLikedItem</h2>
</blockquote>
<pre><code>fun addLikedItem(item: SearchItemModel) {
        if (!likedItems.contains(item)) {
            likedItems.add(item)
        }
    }</code></pre><blockquote>
<h2 id="removelikeditem">removeLikedItem</h2>
</blockquote>
<pre><code>fun removeLikedItem(item: SearchItemModel) {
        likedItems.remove(item)
    }</code></pre><h1 id="search">Search</h1>
<h2 id="searchfragment">SearchFragment</h2>
<blockquote>
<h3 id="setupviews">setUpViews</h3>
</blockquote>
<pre><code>private fun setupViews() {
        // RecyclerView 설정
        gridmanager = StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL)
        binding.rvSearch.layoutManager = gridmanager
        .
        adapter = SearchAdapter(mContext)
        binding.rvSearch.adapter = adapter
        binding.rvSearch.itemAnimator = null
        .
        // 최근 검색어를 가져와 EditText에 설정
        val lastSearch = Utils.getLastSearch(requireContext())
        binding.etSearch.setText(lastSearch)
    }</code></pre><blockquote>
<h3 id="setuplisteners">setUpListeners</h3>
</blockquote>
<pre><code>private fun setupListeners() {
        val imm = requireActivity().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
        binding.btnSearch.setOnClickListener {
            val query = binding.etSearch.text.toString()
            if (query.isNotEmpty()) {
                Utils.saveLastSearch(requireContext(), query)
                adapter.clearItem()
                fetchImageResults(query)
            } else {
                Toast.makeText(mContext, &quot;검색어를 입력해 주세요.&quot;, Toast.LENGTH_SHORT).show()
            }
            // 키보드 숨기기
            imm.hideSoftInputFromWindow(binding.etSearch.windowToken, 0)
        }
    }</code></pre><blockquote>
<h3 id="fetchimageresults">fetchImageResults</h3>
</blockquote>
<pre><code>private fun fetchImageResults(query: String) {
        apiService.image_search(Constants.AUTH_HEADER, query, &quot;recency&quot;, 1, 80)
            ?.enqueue(object : Callback&lt;ImageModel?&gt; {
                override fun onResponse(call: Call&lt;ImageModel?&gt;, response: Response&lt;ImageModel?&gt;) {
                    response.body()?.meta?.let { meta -&gt;
                        if (meta.totalCount &gt; 0) {
                            response.body()!!.documents.forEach { document -&gt;
                                val title = document.displaySitename
                                val datetime = document.datetime
                                val url = document.thumbnailUrl
                                resItems.add(SearchItemModel(title, datetime, url))
                            }
                        }
                    }
                    adapter.items = resItems
                    adapter.notifyDataSetChanged()
                }
                override fun onFailure(call: Call&lt;ImageModel?&gt;, t: Throwable) {
                    Log.e(&quot;#jblee&quot;, &quot;onFailure: ${t.message}&quot;)
                }
            })
    }</code></pre><h2 id="searchadapter">SearchAdapter</h2>
<blockquote>
<h3 id="onbindviewholder">onBindViewHolder</h3>
</blockquote>
<pre><code>override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
        val currentItem = items[position]
        Glide.with(mContext)
            .load(currentItem.url)
            .into(holder.iv_thum_image)
        holder.iv_like.visibility = if (currentItem.isLike) View.VISIBLE else View.INVISIBLE
        holder.tv_title.text = currentItem.title
        holder.tv_datetime.text = getDateFromTimestampWithFormat(
            currentItem.dateTime,
            &quot;yyyy-MM-dd&#39;T&#39;HH:mm:ss.SSS+09:00&quot;,
            &quot;yyyy-MM-dd HH:mm:ss&quot;
        )
    }</code></pre><h1 id="bookmark">Bookmark</h1>
<h2 id="bookmarkfragment">BookmarkFragment</h2>
<blockquote>
<h3 id=""></h3>
</blockquote>
<pre><code>class BookmarkFragment : Fragment() {
.
    private lateinit var mContext: Context
.
    // 바인딩 객체를 null 허용으로 설정 (프래그먼트의 뷰가 파괴될 때 null 처리하기 위함)
    private var binding: FragmentBookMarkBinding? = null
    private lateinit var adapter: BookmarkAdapter
.
    // 사용자의 좋아요를 받은 항목을 저장하는 리스트
    private var likedItems: List&lt;SearchItemModel&gt; = listOf()
.
    override fun onAttach(context: Context) {
        super.onAttach(context)
        mContext = context
    }
.
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // MainActivity로부터 좋아요 받은 항목을 가져옴
        val mainActivity = activity as MainActivity
        likedItems = mainActivity.likedItems
.
        Log.d(&quot;BookmarkFragment&quot;, &quot;#jblee likedItems size = ${likedItems.size}&quot;)
.
        // 어댑터 설정
        adapter = BookmarkAdapter(mContext).apply {
            items = likedItems.toMutableList()
        }
.
        // 바인딩 및 RecyclerView 설정
        binding = FragmentBookMarkBinding.inflate(inflater, container, false).apply {
            rvBookmark.layoutManager = StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL)
            rvBookmark.adapter = adapter
        }
.
        return binding?.root
    }
.
    override fun onDestroyView() {
        super.onDestroyView()
        // 메모리 누수를 방지하기 위해 뷰가 파괴될 때 바인딩 객체를 null로 설정
        binding = null
    }
}
</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[Android/Kotlin] Fragment -> 액티비티 Data 전달]]></title>
            <link>https://velog.io/@harudevelop2_/AndroidKotlin-Fragment-%EC%95%A1%ED%8B%B0%EB%B9%84%ED%8B%B0-Data-%EC%A0%84%EB%8B%AC</link>
            <guid>https://velog.io/@harudevelop2_/AndroidKotlin-Fragment-%EC%95%A1%ED%8B%B0%EB%B9%84%ED%8B%B0-Data-%EC%A0%84%EB%8B%AC</guid>
            <pubDate>Tue, 30 Jan 2024 11:30:46 GMT</pubDate>
            <description><![CDATA[<h1 id="fragment---activity">Fragment -&gt; Activity</h1>
<p>프래그먼트에서 액티비티로의 데이터 전달.
처음엔 Fragment에서 인터페이스를 생성하고 DetailActivity에서 바로 인터페이스를 상속 받았는데
로그캣에서 이런 오류가 발생하였다.</p>
<blockquote>
<p>RuntimeException MainActivity<del>~</del></p>
</blockquote>
<p>이건 프래그먼트에서 onAttach로 액티비티에서 프래그먼트리스너(인터페이스)가 없으면 throw로 나타나게 하였는데 분명 디테일 액티비티에상속을 시켰는데도 계속 이런 오류가 발생하였다.
찾아보니 메인액티비티에 인터페이스가 상속되지 않아서 발생한 오류라고 하였다.</p>
<p>그래서 데이터의 전달을
Fragment -&gt; MainActivity -&gt; DetailActivity로 진행하였다.</p>
<h1 id="interface생성">Interface생성</h1>
<pre><code>interface FragmentDataListener {
    fun onDataReceived(data: Bundle)
}</code></pre><h1 id="fragment">Fragment</h1>
<h2 id="리스너-전역-변수">리스너 전역 변수</h2>
<pre><code>private var listener : FragmentDataListener? = null</code></pre><h2 id="onattach">onAttach</h2>
<pre><code>override fun onAttach(context: Context) {
        super.onAttach(context)

        if (context is FragmentDataListener) {
            listener = context
        } else {
            throw RuntimeException(&quot;$context must implement FragmentDataListener&quot;)
        }
    }</code></pre><h2 id="onviewcreated">onViewCreated</h2>
<pre><code>val musicalAdapter = MusicalAdapter(dataList)

        musicalAdapter.itemClick = object : MusicalAdapter.ItemClick {
            override fun onClick(view : View, position: Int) {
                val clickedItem =dataList[position]
                val data = Bundle().apply{
                    putParcelable(&quot;musicalItem&quot;, clickedItem)
                }

                listener?.onDataReceived(data)

            }
        }
</code></pre><p>데이터리스트의 항목을 선택하면 그 포지션의 데이터들을 번들에 담아준다.</p>
<h1 id="mainactivity">MainActivity</h1>
<p>interface상속 받아주기.</p>
<h2 id="ondatareceived">onDataReceived</h2>
<pre><code>override fun onDataReceived(data: Bundle) {
        val intent = Intent(this, DetailActivity::class.java)
        intent.putExtra(&quot;musicalItem&quot;, data.getParcelable&lt;Parcelable&gt;(&quot;musicalItem&quot;))
        startActivity(intent)
    }</code></pre><h1 id="detailactivity">DetailActivity</h1>
<pre><code>val musicalItem = intent.getParcelableExtra&lt;Musical&gt;(&quot;musicalItem&quot;)

        binding.DetailIv.setImageResource(musicalItem?.musicalImage ?: 0)
        binding.DetailTopName.text = musicalItem?.musicalName
        binding.DetailLocation.text = musicalItem?.musicalLocation
        binding.DetailName.text = musicalItem?.musicalName
        binding.detailDate.text = musicalItem?.musicalDate</code></pre><p>받아서 사용</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Android/Kotlin] Glide]]></title>
            <link>https://velog.io/@harudevelop2_/AndroidKotlin-Glide</link>
            <guid>https://velog.io/@harudevelop2_/AndroidKotlin-Glide</guid>
            <pubDate>Mon, 29 Jan 2024 02:54:51 GMT</pubDate>
            <description><![CDATA[<h1 id="glide">Glide</h1>
<p>서버에서 이미지를 내려 받아올 때 Glide를 이용하면 더 빨리 가능함.</p>
<h4 id="gradle에-라이브러리-등록">gradle에 라이브러리 등록</h4>
<pre><code>dependencies {

    implementation (&quot;com.github.bumptech.glide:glide:4.12.0&quot;)</code></pre><h4 id="사용-방법">사용 방법</h4>
<pre><code>Glide.with(context)
            .load(mItems[position].thumbnailUrl)
            .into(holder.image) //이미지 받아와서 searchImage에 넣는다!</code></pre><p>load에 리소스 전달하고 into에 이미지 뷰 객체 전달.
override를 사용하면 크기를 조절할 수  있음.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Android/Kotlin] SIMPLEDATEFORMAT]]></title>
            <link>https://velog.io/@harudevelop2_/AndroidKotlin-SIMPLEDATEFORMAT</link>
            <guid>https://velog.io/@harudevelop2_/AndroidKotlin-SIMPLEDATEFORMAT</guid>
            <pubDate>Sun, 28 Jan 2024 11:32:39 GMT</pubDate>
            <description><![CDATA[<h1 id="simpledateformat">SimpleDateFormat</h1>
<p>SimpleDateFormat은 첫번째 인자로 시간을 넣어주고 두번째 인자로 시간대, 지역설정을 할 수 있다. Locale는 지역 설정이다</p>
<pre><code>fun main() {
    val currentTime = LocalDateTime.now()
    println(&quot;현재날짜&amp;시간 $currentTime&quot;)

    val formatter = DateTimeFormatter.ofPattern(&quot;yyyy-MM-dd HH:mm:ss&quot;, Locale.getDefault())
    val formatter2 = DateTimeFormatter.ofPattern(&quot;yyyy-MM-dd HH:mm&quot;, Locale.getDefault())
    val formatter3 = DateTimeFormatter.ofPattern(&quot;yyyy-MM-dd HH&quot;, Locale.getDefault())
    val formattedDate = currentTime.format(formatter)
    val formattedDate2 = currentTime.format(formatter2)
    val formattedDate3 = currentTime.format(formatter3)

    println(&quot;포맷된 날짜&amp;시간(년,월,일,시간,분,초): ${formattedDate}초&quot;)
    println(&quot;포맷된 날짜&amp;시간(년,월,일,시간,분): ${formattedDate2}분&quot;)
    println(&quot;포맷된 날짜&amp;시간(년,월,일,시간): ${formattedDate3}시&quot;)
}</code></pre><p><img src="https://velog.velcdn.com/images/harudevelop2_/post/b6d4aca7-d60a-4f85-a38a-4cf73f140230/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Android/Kotlin]Retrofit을 활용한 Kakao api로 이미지 받아오기 과제]]></title>
            <link>https://velog.io/@harudevelop2_/AndroidKotlinRetrofit%EC%9D%84-%ED%99%9C%EC%9A%A9%ED%95%9C-Kakao-api%EB%A1%9C-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EB%B0%9B%EC%95%84%EC%98%A4%EA%B8%B0-%EA%B3%BC%EC%A0%9C</link>
            <guid>https://velog.io/@harudevelop2_/AndroidKotlinRetrofit%EC%9D%84-%ED%99%9C%EC%9A%A9%ED%95%9C-Kakao-api%EB%A1%9C-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EB%B0%9B%EC%95%84%EC%98%A4%EA%B8%B0-%EA%B3%BC%EC%A0%9C</guid>
            <pubDate>Fri, 26 Jan 2024 10:19:47 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/harudevelop2_/post/241ef661-7fbe-4fe4-ac3f-fe54df37c516/image.jpg" alt=""></p>
<p>컨셉 유지하기 위해 중간중간 얼렁뚱땅 넘어가는 부분이 있어요. 절ㄷ ㅐ 귀찮아서 그런거아니고 몰라서 그런건 맞음</p>
<p>처음부터 끝까지 과정을 무에서 유로 흘러가는 과정을 담아볼까 한다. 하나하나 세세하게 한달 뒤 또 까먹고 전전긍긍 하고있을게 분명하기 때문에ㅎㅎ</p>
<h1 id="1일차--ui구성-및-기능">1일차 : UI구성 및 기능..</h1>
<h1 id="우선-viewpager2와-tablayout-활용">우선 viewPager2와 tabLayout 활용</h1>
<p>*<em>gradle에 추가 *</em></p>
<pre><code>    //viewPager2
    implementation(&quot;androidx.viewpager2:viewpager2:1.0.0&quot;)</code></pre><p><strong>레트로핏 때 필요한거 미리 넣어놓기</strong></p>
<pre><code>    //retrofit
    implementation(&quot;com.google.code.gson:gson:2.10.1&quot;)
    implementation(&quot;com.squareup.retrofit2:retrofit:2.9.0&quot;)
    implementation(&quot;com.squareup.retrofit2:converter-gson:2.9.0&quot;)
    implementation(&quot;com.squareup.okhttp3:okhttp:4.10.0&quot;)
    implementation(&quot;com.squareup.okhttp3:logging-interceptor:4.10.0&quot;)
</code></pre><p><strong>인터넷 권한 받아오기 그냥 한번에 넣어놓을게요 manifest에 넣으면 됨.</strong></p>
<pre><code>&lt;uses-permission android:name=&quot;android.permission.INTERNET&quot;/&gt;</code></pre><p>오키 여기까지 하고 까먹은거 있으면 추가로 넣어야지
fragment필요한거 생성해주시고 나는 이미지검색프래크먼트랑 저장소 프래그먼트 2개 만듬.</p>
<h2 id="viewpageradapter">viewPagerAdapter</h2>
<pre><code>package com.android.searchproject

import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.viewpager2.adapter.FragmentStateAdapter

class ViewPagerAdapter(fragmentActivity: FragmentActivity) : FragmentStateAdapter(fragmentActivity) {

    var fragments : ArrayList&lt;Fragment&gt; = ArrayList()

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

    override fun createFragment(position: Int): Fragment {
        return fragments[position]
    }

    fun addFragment(fragment: Fragment){
        fragments.add(fragment)
        notifyItemInserted(fragments.size - 1)
    }

    fun removeFragment(){
        fragments.removeLast()
        notifyItemRemoved(fragments.size)
    }
}</code></pre><h2 id="mainactivitykt">MainActivity.kt</h2>
<pre><code>package com.android.searchproject

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.viewpager2.widget.ViewPager2
import com.android.searchproject.databinding.ActivityMainBinding
import com.google.android.material.tabs.TabLayoutMediator

class MainActivity : AppCompatActivity() {


    private lateinit var binding: ActivityMainBinding


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        initViewPager()
    }
    private fun initViewPager() {
        var viewPager2Adapter = ViewPagerAdapter(this)
        viewPager2Adapter.addFragment(SearchFragment())
        viewPager2Adapter.addFragment(StorageFragment())

        binding.viewPager.apply {
            adapter = viewPager2Adapter

            registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
                override fun onPageSelected(position: Int) {
                    super.onPageSelected(position)
                }
            })
        }
        TabLayoutMediator(binding.tapLayout, binding.viewPager) { tab, position -&gt;
            when (position) {
                0 -&gt; {
                    tab.text = &quot;이미지 검색&quot;
                }
                1 -&gt; {
                    tab.text = &quot;좋아요 보관함&quot;
                }
            }
        }.attach()

    }
}</code></pre><h2 id="mainactivityxml">MainActivity.xml</h2>
<pre><code>&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;androidx.constraintlayout.widget.ConstraintLayout xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    xmlns:app=&quot;http://schemas.android.com/apk/res-auto&quot;
    xmlns:tools=&quot;http://schemas.android.com/tools&quot;
    android:layout_width=&quot;match_parent&quot;
    android:layout_height=&quot;match_parent&quot;
    tools:context=&quot;.MainActivity&quot;&gt;

    &lt;androidx.appcompat.widget.Toolbar
        android:id=&quot;@+id/toolbar&quot;
        android:layout_width=&quot;match_parent&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:background=&quot;@color/black&quot;
        app:layout_constraintEnd_toEndOf=&quot;parent&quot;
        app:layout_constraintStart_toStartOf=&quot;parent&quot;
        app:layout_constraintTop_toTopOf=&quot;parent&quot; &gt;

        &lt;TextView
            android:layout_width=&quot;wrap_content&quot;
            android:layout_height=&quot;wrap_content&quot;
            android:layout_gravity=&quot;center&quot;
            android:text=&quot;HaruSearch&quot;
            android:textColor=&quot;@color/white&quot;
            android:textSize=&quot;30sp&quot;
            android:textStyle=&quot;bold&quot; /&gt;
    &lt;/androidx.appcompat.widget.Toolbar&gt;

    &lt;androidx.constraintlayout.widget.ConstraintLayout
        android:id=&quot;@+id/constraintLayout2&quot;
        android:layout_width=&quot;match_parent&quot;
        android:layout_height=&quot;50dp&quot;
        android:background=&quot;#C5C5C5&quot;
        android:paddingHorizontal=&quot;10dp&quot;
        app:layout_constraintEnd_toEndOf=&quot;parent&quot;
        app:layout_constraintStart_toStartOf=&quot;parent&quot;
        app:layout_constraintTop_toBottomOf=&quot;@+id/toolbar&quot;&gt;

        &lt;EditText
            android:id=&quot;@+id/et_main_search&quot;
            android:layout_width=&quot;0dp&quot;
            android:layout_height=&quot;40dp&quot;
            android:paddingStart=&quot;5dp&quot;

            android:hint=&quot; 키워드&quot;
            android:layout_marginEnd=&quot;10dp&quot;
            app:layout_constraintBottom_toBottomOf=&quot;parent&quot;
            app:layout_constraintEnd_toStartOf=&quot;@+id/btn_main_search&quot;
            app:layout_constraintStart_toStartOf=&quot;parent&quot;
            app:layout_constraintTop_toTopOf=&quot;parent&quot; /&gt;

        &lt;Button
            android:id=&quot;@+id/btn_main_search&quot;
            android:layout_width=&quot;100dp&quot;
            android:layout_height=&quot;wrap_content&quot;
            android:text=&quot;검색&quot;
            android:backgroundTint=&quot;#505050&quot;
            android:textSize=&quot;16sp&quot;
            app:layout_constraintBottom_toBottomOf=&quot;parent&quot;
            app:layout_constraintEnd_toEndOf=&quot;parent&quot;
            app:layout_constraintTop_toTopOf=&quot;parent&quot; /&gt;
    &lt;/androidx.constraintlayout.widget.ConstraintLayout&gt;






    &lt;androidx.viewpager2.widget.ViewPager2
        android:id=&quot;@+id/viewPager&quot;
        android:layout_width=&quot;match_parent&quot;
        android:layout_height=&quot;0dp&quot;
        app:layout_constraintStart_toStartOf=&quot;parent&quot;
        app:layout_constraintEnd_toEndOf=&quot;parent&quot;
        app:layout_constraintTop_toBottomOf=&quot;@+id/constraintLayout2&quot;
        app:layout_constraintBottom_toTopOf=&quot;@+id/tapLayout&quot;/&gt;



    &lt;com.google.android.material.tabs.TabLayout
        android:id=&quot;@+id/tapLayout&quot;
        android:layout_width=&quot;409dp&quot;
        android:layout_height=&quot;wrap_content&quot;
        tools:layout_editor_absoluteX=&quot;1dp&quot;
        tools:layout_editor_absoluteY=&quot;682dp&quot;
        app:layout_constraintBottom_toBottomOf=&quot;parent&quot;
        app:layout_constraintStart_toStartOf=&quot;parent&quot;
        app:layout_constraintEnd_toEndOf=&quot;parent&quot;/&gt;

&lt;/androidx.constraintlayout.widget.ConstraintLayout&gt;</code></pre><p>Search_recyclerview.xml</p>
<pre><code>&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;androidx.constraintlayout.widget.ConstraintLayout xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    xmlns:app=&quot;http://schemas.android.com/apk/res-auto&quot;
    xmlns:tools=&quot;http://schemas.android.com/tools&quot;
    android:layout_width=&quot;match_parent&quot;
    android:layout_height=&quot;wrap_content&quot;
    android:padding=&quot;15dp&quot;&gt;

    &lt;ImageView
        android:id=&quot;@+id/search_Image&quot;
        android:layout_width=&quot;match_parent&quot;
        android:layout_height=&quot;180dp&quot;
        android:scaleType=&quot;centerCrop&quot;
        app:layout_constraintEnd_toEndOf=&quot;parent&quot;
        app:layout_constraintStart_toStartOf=&quot;parent&quot;
        app:layout_constraintTop_toTopOf=&quot;parent&quot; /&gt;

    &lt;ImageView
        android:id=&quot;@+id/search_favorite&quot;
        android:layout_width=&quot;30dp&quot;
        android:layout_height=&quot;30dp&quot;
        android:layout_marginTop=&quot;5dp&quot;
        android:layout_marginEnd=&quot;5dp&quot;
        android:visibility=&quot;gone&quot;
        android:src=&quot;@drawable/favorite&quot;
        app:layout_constraintEnd_toEndOf=&quot;@+id/search_Image&quot;
        app:layout_constraintTop_toTopOf=&quot;@+id/search_Image&quot; /&gt;

    &lt;TextView
        android:id=&quot;@+id/search_name&quot;
        tools:text=&quot;name&quot;
        android:layout_width=&quot;match_parent&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:layout_marginStart=&quot;1dp&quot;
        android:layout_marginTop=&quot;2dp&quot;
        android:textSize=&quot;20sp&quot;
        android:textStyle=&quot;bold&quot;
        app:layout_constraintEnd_toEndOf=&quot;parent&quot;
        app:layout_constraintStart_toStartOf=&quot;parent&quot;
        app:layout_constraintTop_toBottomOf=&quot;@+id/search_Image&quot;/&gt;

    &lt;TextView
        android:id=&quot;@+id/search_datetime&quot;
        tools:text=&quot;dateandTime&quot;
        android:layout_width=&quot;match_parent&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:layout_marginStart=&quot;1dp&quot;
        android:textSize=&quot;15sp&quot;
        app:layout_constraintEnd_toEndOf=&quot;parent&quot;
        app:layout_constraintStart_toStartOf=&quot;parent&quot;
        app:layout_constraintTop_toBottomOf=&quot;@+id/search_name&quot; /&gt;
&lt;/androidx.constraintlayout.widget.ConstraintLayout&gt;</code></pre><p>이렇게 일단 대충 UI를 구성해주었다.</p>
<h1 id="kakao-api-받아오기">Kakao API 받아오기</h1>
<p><img src="https://velog.velcdn.com/images/harudevelop2_/post/0bb5f3f4-5579-4866-ba59-bced8353583b/image.png" alt=""></p>
<p>나의 어플리케이션을 생성해서 받아와준다.</p>
<p><img src="https://velog.velcdn.com/images/harudevelop2_/post/29c57282-41e4-41a3-9b70-d2276f67c6a7/image.png" alt=""></p>
<h1 id="search-dataclass-생성해주기">Search DataClass 생성해주기.</h1>
<p><img src="https://velog.velcdn.com/images/harudevelop2_/post/e96ca629-b312-413f-9e16-1ddc071e8eda/image.png" alt="">
데이터 클래스를 작성할 때는 카카오에서 제공해주는 문서에 들어가면 친절하게 이름과 타입을 표로 만들어 놓았다. 우리는 이걸 보고 옮겨 적어주기만 하면된다.</p>
<pre><code>package com.android.searchproject

import android.os.Parcelable
import android.provider.DocumentsContract
import com.google.gson.annotations.SerializedName

data class SearchResponse(
    @SerializedName(&quot;documents&quot;)
    val documents: MutableList&lt;DocumentsContract.Document&gt;?,
    @SerializedName(&quot;meta&quot;)
    val metaData: MetaData?
)

data class MetaData(
    @SerializedName(&quot;total_count&quot;)
    val totalCount : Int,
    @SerializedName(&quot;pageable_count&quot;)
    val pageableCount : Int,
    @SerializedName(&quot;is_end&quot;)
    val isEnd : Boolean
)

@Parcelize
data class Document(
    @SerializedName(&quot;collection&quot;)
    val collection: String,
    @SerializedName(&quot;datetime&quot;)
    val dateTime: String,
    @SerializedName(&quot;display_sitename&quot;)
    val displaySiteName: String,
    @SerializedName(&quot;doc_url&quot;)
    val docUrl: String,
    @SerializedName(&quot;height&quot;)
    val height: Int,
    @SerializedName(&quot;image_url&quot;)
    val imageUrl: String,
    @SerializedName(&quot;thumbnail_url&quot;)
    val thumbnailUrl: String,
    @SerializedName(&quot;width&quot;)
    val width: Int,
): Parcelable</code></pre><h3 id="parcelable오류-해결">Parcelable오류 (해결!!)</h3>
<p>Parcelable이 정의가 되지 않았다는 오류가 생겼다.</p>
<pre><code>plugins {
    id(&quot;com.android.application&quot;)
    id(&quot;org.jetbrains.kotlin.android&quot;)
    id (&quot;kotlin-parcelize&quot;)
    id (&quot;kotlin-kapt&quot;)
}</code></pre><p>플러그인에 밑에 2줄을 추가해주니 해결 됨.</p>
<h3 id="serializedname이-무엇인가요"><strong>@SerializedName</strong>이 무엇인가요?</h3>
<p>아 찾아봤는데 정리하기 귀찮다. 나중에 생각나면 다시 정리해야지</p>
<h1 id="retrofit-객체-생성-networkclientkt">Retrofit 객체 생성, NetworkClient.kt</h1>
<p>레트로핏에 대한 자세한 내용은 이전에 작성한 자료를 보시면 됩니다.
(아직 작성 안했고 이 자리에 작성하는대로 링크 넣어야징~~)</p>
<pre><code>object NetWorkClient {

    private const val BASE_URL = &quot;https://dapi.kakao.com/&quot;


    private fun createOkHttpClient(): OkHttpClient {
        val interceptor = HttpLoggingInterceptor()

        if (BuildConfig.DEBUG)
            interceptor.level = HttpLoggingInterceptor.Level.BODY
        else
            interceptor.level = HttpLoggingInterceptor.Level.NONE

        return OkHttpClient.Builder()
            .connectTimeout(20, TimeUnit.SECONDS)
            .readTimeout(20, TimeUnit.SECONDS)
            .writeTimeout(20, TimeUnit.SECONDS)
            .addNetworkInterceptor(interceptor)
            .build()
    }

    private val searchRetrofit = Retrofit.Builder()
        .baseUrl(BASE_URL)//
        .addConverterFactory(GsonConverterFactory.create()).client(
            createOkHttpClient()
        ).build()

    val searchNetwork: NetworkInterface = searchRetrofit.create(NetworkInterface::class.java)

}</code></pre><p>Base_URL의 코드는 기본정보의 URL에서 가지고 오면 된다. (위에 사진 있음.)</p>
<h1 id="networkinterface">NetworkInterface</h1>
<pre><code>interface NetworkInterface {

    @Headers(&quot;Authorization: API&quot;)
    @GET(&quot;v2/search/image&quot;)
    suspend fun searchImage(@QueryMap param: HashMap&lt;String, String&gt;) : SearchResponse
}</code></pre><p>API에는 어플리케이션 생성하고 받은 REST_API 넣으면 됨.</p>
<h1 id="searchadapter">SearchAdapter</h1>
<pre><code>package com.android.searchproject

import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
import com.android.searchproject.databinding.SearchRecyclerviewBinding
import com.bumptech.glide.Glide
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale

class SearchAdapter(private val context: Context, private val results: MutableList&lt;Document&gt;, private val favoriteItems: MutableList&lt;Document&gt;) :
    RecyclerView.Adapter&lt;SearchAdapter.SearchViewHolder&gt;() {

    interface SearchThumbnailClickListener {
        fun onClick(view: View, position: Int)
    }

    var searchThumbnailClickListener: SearchThumbnailClickListener? = null

    private val inputFormat = SimpleDateFormat(&quot;yyyy-MM-dd&#39;T&#39;HH:mm:ss.SSSXXX&quot;, Locale.getDefault())
    private val outputFormat = SimpleDateFormat(&quot;yyyy-MM-dd HH:mm:ss&quot;, Locale.getDefault())

    inner class SearchViewHolder(private val binding: SearchRecyclerviewBinding) :
        RecyclerView.ViewHolder(binding.root) {
        val image = binding.searchImage
        val name = binding.searchName
        val dateTime = binding.searchDatetime
        val favorite = binding.searchFavorite
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SearchViewHolder {
        return SearchViewHolder(
            SearchRecyclerviewBinding.inflate(
                LayoutInflater.from(parent.context),
                parent,
                false
            )
        )
    }

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

    override fun onBindViewHolder(holder: SearchViewHolder, position: Int) {
        holder.image.setOnClickListener {
            searchThumbnailClickListener?.onClick(holder.favorite, position)
        }

        Glide.with(context)
            .load(results[position].thumbnailUrl)
            .into(holder.image)
        holder.name.text = results[position].displaySiteName
        holder.dateTime.text =
            outputFormat.format(inputFormat.parse(results[position].dateTime) as Date)
        holder.favorite.isVisible = (favoriteItems.find { it == results[position] } != null)
    }
}</code></pre><h1 id="searchfragment">SearchFragment</h1>
<p>아 몰라</p>
<p><img src="https://velog.velcdn.com/images/harudevelop2_/post/0f1ef298-c82e-4786-97c3-3694d90b0ccd/image.jpg" alt=""></p>
<h1 id="2일차">2일차</h1>
<p>우선 어제처럼 TIL을 작성하면 너무 비효율적인 것 같음.
오늘은 주말이지만 어제 해결하지 못한 오류가 계속 생각나서 코드 쳐다 봄.</p>
<h1 id="오류-viewpager2-does-not-support-direct-child-views">오류 (ViewPager2 does not support direct child views)</h1>
<p><img src="https://velog.velcdn.com/images/harudevelop2_/post/213a3199-4097-49cf-9d1c-1ac740ac27c3/image.png" alt="">
처음엔 setFragment를 viewPager에 진행했었음.
애뮬레이터 실행은 되는데 검색어에 입력하고 검색버튼을 누르면 앱이 꺼지는 오류가 발생함</p>
<p><img src="https://velog.velcdn.com/images/harudevelop2_/post/2fd85d89-2091-4c66-974e-1000534f87ae/image.png" alt="">
이렇게 사이트 검색은 가능하지만 ViewPager2 does not support direct child views와 같은 오류가 발생함. ViewPager2위에 직접적으로 자식뷰를 생성하는건 안된다고 한다</p>
<p>main_activity_xml. 그래서 뷰페이저를 프레임 레이아웃으로 감싸고</p>
<pre><code>&lt;FrameLayout
        android:id=&quot;@+id/frameLayout&quot;
        android:layout_width=&quot;match_parent&quot;
        android:layout_height=&quot;0dp&quot;
        app:layout_constraintStart_toStartOf=&quot;parent&quot;
        app:layout_constraintEnd_toEndOf=&quot;parent&quot;
        app:layout_constraintTop_toBottomOf=&quot;@+id/constraintLayout2&quot;
        app:layout_constraintBottom_toTopOf=&quot;@+id/tapLayout&quot;&gt;

    &lt;androidx.viewpager2.widget.ViewPager2
        android:id=&quot;@+id/viewPager&quot;
        android:layout_width=&quot;match_parent&quot;
        android:layout_height=&quot;0dp&quot; /&gt;

    &lt;/FrameLayout&gt;</code></pre><p>frameLayout위에 리사이클러뷰가 올라가질 수 있도록 함.</p>
<p><img src="https://velog.velcdn.com/images/harudevelop2_/post/212840bb-dee3-4a75-bccd-c454c1ef299f/image.png" alt=""></p>
<p>검색이 가능해졌다!!</p>
<p>일단 오늘은 여기까지 하고 3일차 목표</p>
<h3 id="3일차-목표">3일차 목표</h3>
<p><strong>이미지 클릭시 좋아요 보관함으로 이동</strong>
<strong>키보드 숨김처리</strong>
<strong>엔터 클릭 시 검색</strong>
<strong>리스트에서 특정 이미지를 선택하면 특별한 표시를 보여주도록 구현합니다</strong></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Android/Kotlin/Algorithm/프로그래머스] level0. 마지막 원소  [feat. addAll]]]></title>
            <link>https://velog.io/@harudevelop2_/AndroidKotlinAlgorithm%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-level0.-%EB%A7%88%EC%A7%80%EB%A7%89-%EC%9B%90%EC%86%8C-feat.-addAll</link>
            <guid>https://velog.io/@harudevelop2_/AndroidKotlinAlgorithm%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-level0.-%EB%A7%88%EC%A7%80%EB%A7%89-%EC%9B%90%EC%86%8C-feat.-addAll</guid>
            <pubDate>Fri, 26 Jan 2024 01:04:27 GMT</pubDate>
            <description><![CDATA[<h1 id="문제설명">문제설명</h1>
<p><img src="https://velog.velcdn.com/images/harudevelop2_/post/5300e991-8b82-404b-9fed-38fdc69b872d/image.png" alt=""></p>
<h4 id="간단-요약">간단 요약</h4>
<p>마지막 인덱스의 값이 바로 앞 인덱스의 값보다 작으면 마지막 값 - 그전 값
크면 마지막 값 * 2</p>
<h1 id="문제-풀이">문제 풀이</h1>
<p>음.. 마지막 인덱스를 어떻게 표현할지 몰라서 그냥 num_list[num_list.lastIndex] 이런식으로 표현 했다.
마지막 인덱스와 그 앞 인덱스로 변수로 설정해주면 쉽게 풀수 있는 문제였다.
처음엔 answer에 lastNl -nl1 값만 넣어줘서 출력값이 [5] , [10] 으로 밖에 안나왔다.
그래서 numList.add(lastNl- nl1) answer.add(num_List)이런  식으로 표현했다.ㅋㅋ몽총이
잉 어찌 넣어줘야지...하고 구글에 검색해보니 <strong>addAll</strong>이란 함수가 있었다.</p>
<pre><code>class Solution {
    fun solution(num_list: IntArray): IntArray {
        val answer = ArrayList&lt;Int&gt;()
        var lastNl = num_list[num_list.lastIndex]
        var nl1 = num_list[num_list.lastIndex - 1]

        if(lastNl &gt; nl1) {
             answer.addAll(num_list.toList() + (lastNl - nl1))
        } else {
            answer.addAll(num_list.toList() + (lastNl * 2))
        }
        return answer.toIntArray()
    }
}</code></pre><h3 id="addall">addAll</h3>
<p>addAll은 ArrayList간 데이터 복사 할수 있다고 한다.
그냥 배열을 복사한다고 생각하면 된다고 한다.</p>
<p><img src="https://velog.velcdn.com/images/harudevelop2_/post/306dd239-3c20-471f-ac9e-dc7eebb320a3/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/harudevelop2_/post/2ada0f97-9fea-490a-a92d-9b20d675d937/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Android/Kotlin] MVVM패턴?]]></title>
            <link>https://velog.io/@harudevelop2_/AndroidKotlin-MVVM%ED%8C%A8%ED%84%B4</link>
            <guid>https://velog.io/@harudevelop2_/AndroidKotlin-MVVM%ED%8C%A8%ED%84%B4</guid>
            <pubDate>Thu, 25 Jan 2024 10:06:37 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/harudevelop2_/post/716faa47-c321-4783-bed5-922d3443a4a7/image.png" alt=""></p>
<h1 id="mvvm패턴">MVVM패턴?</h1>
<p>카카오API를 이용해서 데이터를 가지고 오는 과제를 하고 있었는데 VIEW MODEL을 알게 되었고 MVVM패턴에서 파생된거라고 하길래 또 MVVM패턴이 뭔지 궁금해져서 정리하게 되었다.
<img src="https://velog.velcdn.com/images/harudevelop2_/post/a1f0c24c-bab5-4d53-bffe-8ff91f8a9242/image.png" alt=""></p>
<p>음...이게 뭘까??... 봐도 모르겠는 걸...!!
일단 어떤 것인지만 알아놓고 지금 하고 있는 과제 끝나고 시간 남으면 더 알아봐야겠다.</p>
<h2 id="디자인-패턴">디자인 패턴</h2>
<p>MVC, MVP, MVVM 이렇게 3개가 소프트웨어에서 자주 사용하는 디자인 패턴이라고 한다.</p>
<h2 id="mvc-패턴">MVC 패턴</h2>
<p>Model, View, Controller 3개가 이루어져 동작한다고 한다. 평소에 우리가 사용하는 것이 MVC패턴으로 보편적으로 사용 되지만 앱이 커질수록 유지보수가 어려워진다고 한다.
<img src="https://velog.velcdn.com/images/harudevelop2_/post/92a0400b-9374-457b-819d-b91b8bd8131a/image.png" alt=""></p>
<h3 id="model">Model</h3>
<p>view에  표시되기 위한 데이터</p>
<h3 id="view">view</h3>
<p>XML, HTML과 같은 UI 구성요소
controller 부터 받은 UI데이터를 표시하는 역할을 함.</p>
<h3 id="controller">controller</h3>
<p>사용자의 요청을 처리하는 역할을 담당
Model을 통해 받은 데이터를 처리하거나, 결과 값을 View에 반환하는 역할
일반적으로 View와 Model사이를 중재하는 역할</p>
<h2 id="mvp-패턴">MVP 패턴</h2>
<p><img src="https://velog.velcdn.com/images/harudevelop2_/post/3d58c16f-a829-4cea-8135-f752ceed9590/image.png" alt=""></p>
<p>여기서는 controller 대신 presenter라는 아이가 역할을 다한다.</p>
<h3 id="present">present</h3>
<p>view를 통해 사용자의 입력을 받고 Model에 도움을 받아 사용자의 데이터를 처리하고 결과를 View로 다시 전달. present는 interface를 통해 View와 상호작용.</p>
<h2 id="mvvm-패턴">MVVM 패턴</h2>
<p><img src="https://velog.velcdn.com/images/harudevelop2_/post/8b23e333-6dd0-4c9c-93b4-11fbf47bc77d/image.png" alt=""></p>
<h3 id="viewmodel">ViewModel</h3>
<p>ViewModel는 View 상태를 유지 및 변화시키고, View에 대한 작업의 결과로 Model을 조작하고, View에서 발생되는 이벤트를 트리거하는데 도움이 되는 메서드, 명령, 또는 다른 속성들을 노출하는 역할을 합니다.</p>
<p>View는 ViewModel에 관한 참조를 가지고 있지만, ViewModel은 View에 관한 정보를 모릅니다. 이는 View와 ViewModel사이의 n:1의 의존관계가 생기며 다수의 View는 하나의 ViewModel에 매핑될 수 있습니다. 이로서 ViewModel은 다수의 View에 대해 완전히 독립적입니다.</p>
<p>안드로이드에서 단방향 혹은 양방향 데이터 바인딩을 사용한다면 ViewModel의 속성들과 View와 동기화를 보장할 수 있습니다.</p>
<p>[참고자료]...및 복붙...
<a href="https://velog.io/@blucky8649/MVC-MVP-MVVM-%ED%8C%A8%ED%84%B4%EC%9D%98-%ED%8A%B9%EC%A7%95">https://velog.io/@blucky8649/MVC-MVP-MVVM-%ED%8C%A8%ED%84%B4%EC%9D%98-%ED%8A%B9%EC%A7%95</a></p>
<p><img src="https://velog.velcdn.com/images/harudevelop2_/post/22d8b75c-09e0-478b-a822-55f0b5bfd981/image.png" alt="">
뭐라는거야... 이렇게 글로만 보니 이해가 안된다ㅠㅠ 실습을 통해 돌아오겠읍니다..총총..
이해가 안되니까 대충 쓰게 됨.. 캵캵캵캵
티스토리 쓰다가 velog엄청 이쁘길래 넘어왔는데 마음에 드네</p>
]]></description>
        </item>
    </channel>
</rss>