<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Big Nerd Ranch</title>
    <link>https://bignerdranch.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Thu, 2 Jul 2026 05:48:16 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>Kaboomba</managingEditor>
    <item>
      <title>SharedPreferences Property Delegate</title>
      <link>https://bignerdranch.tistory.com/79</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Without property delegation&lt;/p&gt;
&lt;pre id=&quot;code_1714571153417&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class UserSettings(
   private val prefs: SharedPreferences
) {
   
   var name: String 
   	 get() = prefs.getString(&quot;name&quot;, &quot;&quot;) ?: &quot;&quot;
	 set(value) {
         prefs.edit {
            putString(&quot;name&quot;, value)
         }
     }
   
   var age: Int 
   	 get() = prefs.getInt(&quot;age&quot;, 0) ?:0
	 set(value) {
         prefs.edit {
            putInt(&quot;name&quot;, value)
         }
     } 
}&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;Using delegate looks much cleaner&lt;/p&gt;
&lt;pre id=&quot;code_1714571218019&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class UserSettings(
    private val prefs: SharedPreferences
) {

    var name by prefs.string()

    var age by prefs.int()
    
}&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;Add generic delegate&lt;/p&gt;
&lt;pre id=&quot;code_1714571258035&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class SharedPreferencesDelegate&amp;lt;T&amp;gt;(
    private val delegate: SharedPreferences,
    private val key: String? = null,
    private val defaultValue: T,
    private val getter: SharedPreferences.(String, T) -&amp;gt; T?,
    private val setter: SharedPreferences.Editor.(String, T) -&amp;gt; Unit
) : ReadWriteProperty&amp;lt;Any, T&amp;gt; {
    override fun getValue(thisRef: Any, property: KProperty&amp;lt;*&amp;gt;): T {
        return delegate.getter(key ?: property.name, defaultValue) ?: defaultValue
    }

    override fun setValue(thisRef: Any, property: KProperty&amp;lt;*&amp;gt;, value: T) {
        delegate.edit {
            setter(key ?: property.name, value)
        }
    }
}&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;Exensions for supported type&lt;/p&gt;
&lt;pre id=&quot;code_1714571286744&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun SharedPreferences.string(
    key: String? = null,
    defaultValue: String = &quot;&quot;
) = SharedPreferencesDelegate(
    delegate = this,
    key = key,
    defaultValue = defaultValue,
    getter = SharedPreferences::getString,
    setter = SharedPreferences.Editor::putString
)

fun SharedPreferences.int(
    key: String? = null,
    defaultValue: Int = 0
) = SharedPreferencesDelegate(
    delegate = this,
    key = key,
    defaultValue = defaultValue,
    getter = SharedPreferences::getInt,
    setter = SharedPreferences.Editor::putInt
)

fun SharedPreferences.long(
    key: String? = null,
    defaultValue: Long = 0L
) = SharedPreferencesDelegate(
    delegate = this,
    key = key,
    defaultValue = defaultValue,
    getter = SharedPreferences::getLong,
    setter = SharedPreferences.Editor::putLong
)

fun SharedPreferences.float(
    key: String? = null,
    defaultValue: Float = 0f
) = SharedPreferencesDelegate(
    delegate = this,
    key = key,
    defaultValue = defaultValue,
    getter = SharedPreferences::getFloat,
    setter = SharedPreferences.Editor::putFloat
)

fun SharedPreferences.boolean(
    key: String? = null,
    defaultValue: Boolean = false
) = SharedPreferencesDelegate(
    delegate = this,
    key = key,
    defaultValue = defaultValue,
    getter = SharedPreferences::getBoolean,
    setter = SharedPreferences.Editor::putBoolean
)

fun SharedPreferences.stringSet(
    key: String? = null,
    defaultValue: Set&amp;lt;String&amp;gt; = emptySet()
) = SharedPreferencesDelegate(
    delegate = this,
    key = key,
    defaultValue = defaultValue,
    getter = SharedPreferences::getStringSet,
    setter = SharedPreferences.Editor::putStringSet
)&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Kotlin</category>
      <author>Kaboomba</author>
      <guid isPermaLink="true">https://bignerdranch.tistory.com/79</guid>
      <comments>https://bignerdranch.tistory.com/79#entry79comment</comments>
      <pubDate>Wed, 1 May 2024 22:50:03 +0900</pubDate>
    </item>
    <item>
      <title>Debounce using Flow + collectLatest</title>
      <link>https://bignerdranch.tistory.com/76</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Use case&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;When we have filter/search functionality in our app, we often want to trigger an operation whenver user enters query in an EditText and we want to reduce network calls by using debouncing. Kotlin Flow supports debouce operation but it's still preview until 1.6.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/debounce.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/debounce.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1647392464389&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;debounce&quot; data-og-description=&quot;debounce common Returns a flow that mirrors the original flow, but filters out values that are followed by the newer values within the given timeout. The latest value is always emitted. Example: flow { emit(1) delay(90) emit(2) delay(90) emit(3) delay(1010&quot; data-og-host=&quot;kotlin.github.io&quot; data-og-source-url=&quot;https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/debounce.html&quot; data-og-url=&quot;https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/debounce.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/debounce.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/debounce.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&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;debounce&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;debounce common Returns a flow that mirrors the original flow, but filters out values that are followed by the newer values within the given timeout. The latest value is always emitted. Example: flow { emit(1) delay(90) emit(2) delay(90) emit(3) delay(1010&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;kotlin.github.io&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;So, if we use Kotlin lower than 1.6.10, then we are unlikey to use Flow.debounce.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Alternative of this would be collectLatest + delay.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1647392680304&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;suspend fun &amp;lt;T&amp;gt; Flow&amp;lt;T&amp;gt;.collectDebounce(timeMillis: Long, action: suspend (T) -&amp;gt; Unit) {
    this.collectLatest {
        delay(timeMillis)
        action(it)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;The way it works is that collectLatest will cancel the previous collecting if new item arrives. So, we just simply put some delay before collecting. With mutableShardFlow, we can easily acheive deboucing.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1647392978757&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// ViewModel
private val queryFlow = MutableSharedFlow(&quot;&quot;)

init {
   queryFlow
   	.collectDebounce(1000L) {
       fetchData()
   }
}

fun queryEntered(query: String) {
   queryFlow.value = query
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Kotlin/Flow</category>
      <author>Kaboomba</author>
      <guid isPermaLink="true">https://bignerdranch.tistory.com/76</guid>
      <comments>https://bignerdranch.tistory.com/76#entry76comment</comments>
      <pubDate>Wed, 16 Mar 2022 10:09:48 +0900</pubDate>
    </item>
    <item>
      <title>Enum을 이용한 Date 포맷</title>
      <link>https://bignerdranch.tistory.com/75</link>
      <description>&lt;pre id=&quot;code_1647148859459&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.time.LocalDate;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Date;

/**
 * This pattern has the following benefits:
 *
 *  - Date formats are referenced as a strong type
 *  - Code completion on patterns
 *  - Patterns can be javadoc'ed
 */
public enum Dates {

    /**
     * &amp;lt;p&amp;gt;Format: yyyy-MM-dd&amp;lt;/p&amp;gt;
     * &amp;lt;p&amp;gt;Example: 2022-03-30&amp;lt;/p&amp;gt;
     */
    yyyy_MM_dd(&quot;yyyy-MM-dd&quot;),
    
    /**
     * &amp;lt;p&amp;gt;Format: MMM dd, yyyy&amp;lt;/p&amp;gt;
     * &amp;lt;p&amp;gt;Example: Mar 30, 2022&amp;lt;/p&amp;gt;
     */
    MMM_dd_yyyy(&quot;MMM dd, yyyy&quot;);

    private final DateTimeFormatter formatter;

    Dates(final String formatString) {
        formatter = DateTimeFormatter.ofPattern(formatString);
    }

    public LocalDate parse(final String string) {
        return LocalDate.parse(string, formatter);
    }

    public String format(final LocalDate date) {
        return formatter.format(date);
    }

    public Date parseDate(final String string) {
        return Date.from(parse(string).atStartOfDay()
                .atZone(ZoneId.systemDefault())
                .toInstant());
    }

    public String format(final Date date) {
        return format(date.toInstant()
                .atZone(ZoneId.systemDefault())
                .toLocalDate());
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출처: &lt;a href=&quot;https://gist.github.com/dblevins/47f7508e241d775fd7007a4639d6565a&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://gist.github.com/dblevins/47f7508e241d775fd7007a4639d6565a&lt;/a&gt;&lt;/p&gt;</description>
      <category>Java</category>
      <author>Kaboomba</author>
      <guid isPermaLink="true">https://bignerdranch.tistory.com/75</guid>
      <comments>https://bignerdranch.tistory.com/75#entry75comment</comments>
      <pubDate>Sun, 13 Mar 2022 14:21:16 +0900</pubDate>
    </item>
    <item>
      <title>DiffUtil을 사용할 때 Mutable data type의 위험성</title>
      <link>https://bignerdranch.tistory.com/74</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;리사이클러뷰를 사용할 때 성능향상을 위해 DiffUtil을 이용할 것이다. 그런데, DiffUtil을 사용하면서 주의해야할 중의 하나는 ArrayList나 var 멤버가 포함된 mutable한 데이터 타입을 사용하는 것이다. 결론적으로 말하면, 이런 데이터 타입을 사용할 경우 데이터를 변경했음 해도 불구하고 리사이클러뷰가 업데이트 되지 않게 되는 현상에 직면하게 된다.&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;관련 코드를 통해 해당 증상을 살펴보자. 아래와 같이 ListAdapter를 이용해 사용자를 추가하는 간단한 앱이 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot_1647136177.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;2280&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FwvsY/btrvMIWEkda/LaKiP7zwHaTC43mfQtKx01/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FwvsY/btrvMIWEkda/LaKiP7zwHaTC43mfQtKx01/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FwvsY/btrvMIWEkda/LaKiP7zwHaTC43mfQtKx01/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFwvsY%2FbtrvMIWEkda%2FLaKiP7zwHaTC43mfQtKx01%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;351&quot; height=&quot;741&quot; data-filename=&quot;Screenshot_1647136177.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;2280&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;Add를 누르면 사용자를 리사이클러뷰에 추가할 것이다.&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;pre id=&quot;code_1647136322842&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&amp;gt;
&amp;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;
    android:padding=&quot;16dp&quot;
    tools:context=&quot;.MainActivity&quot;&amp;gt;

    &amp;lt;Button
        android:id=&quot;@+id/addButton&quot;
        android:layout_width=&quot;wrap_content&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:text=&quot;Add&quot;
        app:layout_constraintEnd_toEndOf=&quot;parent&quot;
        app:layout_constraintTop_toTopOf=&quot;parent&quot; /&amp;gt;

    &amp;lt;androidx.recyclerview.widget.RecyclerView
        android:id=&quot;@+id/userList&quot;
        android:layout_width=&quot;0dp&quot;
        android:layout_height=&quot;0dp&quot;
        android:layout_marginTop=&quot;16dp&quot;
        app:layoutManager=&quot;androidx.recyclerview.widget.LinearLayoutManager&quot;
        app:layout_constraintBottom_toBottomOf=&quot;parent&quot;
        app:layout_constraintLeft_toLeftOf=&quot;parent&quot;
        app:layout_constraintRight_toRightOf=&quot;parent&quot;
        app:layout_constraintTop_toBottomOf=&quot;@id/addButton&quot; /&amp;gt;

&amp;lt;/androidx.constraintlayout.widget.ConstraintLayout&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1647136372762&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        setupView()
    }

    private val userAdapter by lazy { UserAdapter() }
    private val users = arrayListOf&amp;lt;User&amp;gt;()

    private fun setupView() {
        val userRecyclerView = findViewById&amp;lt;RecyclerView&amp;gt;(R.id.userList)
        userRecyclerView.adapter = userAdapter
        userAdapter.submitList(users)

        val addButton = findViewById&amp;lt;Button&amp;gt;(R.id.addButton)
        addButton.setOnClickListener {
            addUser()
        }
    }

    private fun addUser() {
        users.add(getUser())
        userAdapter.submitList(users)
    }

    private fun getUser(): User {
        val nextId = users.size.inc().toString()
        return User(
            id = nextId,
            name = &quot;User $nextId&quot;,
            email = &quot;user$nextId@email.com&quot;
        )
    }
}


data class User(
    val id: String,
    val name: String,
    val email: String
)


class UserAdapter : ListAdapter&amp;lt;User, UserViewHolder&amp;gt;(DIFF_CALLBACK) {
    companion object {
        private val DIFF_CALLBACK = object : DiffUtil.ItemCallback&amp;lt;User&amp;gt;() {
            override fun areItemsTheSame(oldItem: User, newItem: User): Boolean {
                return oldItem.id == newItem.id
            }

            override fun areContentsTheSame(oldItem: User, newItem: User): Boolean {
                return oldItem == newItem
            }
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserViewHolder {
        val itemView = LayoutInflater.from(parent.context)
            .inflate(android.R.layout.simple_list_item_2, parent, false)
        return UserViewHolder(itemView)
    }

    override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
        val user = getItem(position) ?: return
        holder.bind(user)
    }
}

class UserViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {

    private val text1 by lazy { itemView.findViewById&amp;lt;TextView&amp;gt;(android.R.id.text1) }
    private val text2 by lazy { itemView.findViewById&amp;lt;TextView&amp;gt;(android.R.id.text2) }

    fun bind(user: User) {
        text1.text = user.name
        text2.text = user.email
    }
}&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;Add버튼을 클릭할 때 User를 리사이클러뷰에 추가해는 아주 간단한 코드이다. 위의 코드를 실행해 보면 Add 버튼을 눌러도 화면에는 아무 변화가 없을 것이다. 그러나 디버깅을 해보면 users에는 User가 추가되어 있는 것을 알 수 있다.&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;그럼, 이번에는 users를 ArrayList대신에 List를 사용하고 대신 name과 email을&amp;nbsp; var타입으로 변경한 후 첫번째 User의 name과 email 을 변경한 후 submitList를 해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1647136787383&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;data class User(
    val id: String,
    var name: String,
    var email: String
)

class MainActivity : AppCompatActivity() {
    ...

    private fun setupView() {
        val userRecyclerView = findViewById&amp;lt;RecyclerView&amp;gt;(R.id.userList)
        userRecyclerView.adapter = userAdapter
        users.add(getUser())
        users.add(getUser())
        userAdapter.submitList(users)

        val addButton = findViewById&amp;lt;Button&amp;gt;(R.id.addButton)
        addButton.setOnClickListener {
            updateUser()
        }
    }

	private fun updateUser() {
        val firstUser = users.first()
        firstUser.name = &quot;Alice&quot;
        firstUser.email = &quot;alice@email.com&quot;
        userAdapter.submitList(users)
    }
}&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;User를 2명 추가한 후 Add버튼을 클릭할 때 첫번째 User의 이름과 이메일을 업데이트 한 후 submitList를 해지만, 여전히 리사이클러뷰에는 아무런 변화가 없을 것이다.&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DiffUtil의 구현체인 AsyncListDiffer.java의 소스코드를 보면 아래와 같이 제일 먼저 동일 인스터인지 비교를 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1647137412438&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public void submitList(@Nullable final List&amp;lt;T&amp;gt; newList,
            @Nullable final Runnable commitCallback) {
        // incrementing generation means any currently-running diffs are discarded when they finish
        final int runGeneration = ++mMaxScheduledGeneration;

        if (newList == mList) { // &amp;lt;-- !!!
            // nothing to do (Note - still had to inc generation, since may have ongoing work)
            if (commitCallback != null) {
                commitCallback.run();
            }
            return;
        }

        final List&amp;lt;T&amp;gt; previousList = mReadOnlyList;
        
        ...
        
 }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서, ListAdapter를 사용할 때 MutableList 타입이나 var 멤버를 사용해서 아이템을 추가하거나 변경할 경우라도 == 비교는 동일한 인스터스의 경우 항상&amp;nbsp; true이므로 RecyclerView는 업데이트 되지 않게 된다. 그럼, 이에 대한 해결책을 무엇일까?&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;MutableList대신 List 데이터 타입을 쓰고 변경시 다른&amp;nbsp; List 인스턴스를 리턴하도록 만들면 된다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1647137783604&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private fun addUser() {
	users = users.plus(getUser())
	userAdapter.submitList(users)
}

private fun updateUser(user: User) {
	users = users.map { userItem -&amp;gt;
		if (userItem.id == user.id)
          userItem.copy(name = user.name, email = user.email)
		else
          userItem
	}
	userAdapter.submitList(users)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Android</category>
      <author>Kaboomba</author>
      <guid isPermaLink="true">https://bignerdranch.tistory.com/74</guid>
      <comments>https://bignerdranch.tistory.com/74#entry74comment</comments>
      <pubDate>Sun, 13 Mar 2022 11:16:58 +0900</pubDate>
    </item>
    <item>
      <title>Tiny example - how immutable/readonly class helps OOP</title>
      <link>https://bignerdranch.tistory.com/73</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;I have read about Liskov Substitute Principle(LSP, &lt;a href=&quot;https://en.wikipedia.org/wiki/Liskov_substitution_principle&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://en.wikipedia.org/wiki/Liskov_substitution_principle&lt;/a&gt;) few times and tried to understand correctly but it's clear sometimes but ambiguous other times. I confess it's not&amp;nbsp; still 100 clear. I might need to revisit when I have a problem.&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;We can easily find long discussions about LSP(&lt;a href=&quot;https://stackoverflow.com/questions/56860/what-is-an-example-of-the-liskov-substitution-principle&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://stackoverflow.com/questions/56860/what-is-an-example-of-the-liskov-substitution-principle&lt;/a&gt;) and it helps somehow but some of them makes me more confusing. While doing my reading I realised that one of core issue the example Rectange and Square example comes easily when we use mutable classes.&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;First, let's have a look at typcial example of Rectange and Square.&lt;/p&gt;
&lt;pre id=&quot;code_1645480863582&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Rectangle {
    private int width;
    private int height;
    
    public int getWidth() {
      return this.width;
    }
    
    public void setWidth(int width) {
       this.width = width;
    }
    
    public int getHeight() {
      return this.height;
    }
    
    public void setHeight(int height) {
       this.height = height;
    }
    
    public int getArea() {
      return width * height;
    }
}

public class Square extends Rectangle {

}&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;We face critical issue when we set width or height in Squre class. If we dont' set wdith and height at the same time, width and height will not be the same and it won't be a square anymore. People try to fix the issue in many different apporach and this make the simple example very complicating. To me, main cause of the prolem come from setters, setWidth and setHeight. If we remove setters and use copy then, it can remove this side effect.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1645481426441&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Rectangle {
    private final int width;
    private final int height;
    
    public Rectangle(int width, int height) {
       this.width = width;
       this.height = height;
    }
    
    public int getWidth() {
      return this.width;
    }
    
    public int getHeight() {
      return this.height;
    }
    
    public int getArea() {
      return width * height;
    }
}

public class Square extends Rectangle {

   public Square(int side) {
      super(side, side);
   }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;And we can copy fields when we need to change width or height.&lt;/p&gt;
&lt;pre id=&quot;code_1645481615566&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Rectangle rectangle = new Square(10);
Rectangle newRectangle = new Rectangle(rectangle.width, 20);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;There is no other way to modify its fields and won't break its original intention.&lt;/p&gt;</description>
      <category>Object Oriented Programming/Coding Practice 101</category>
      <author>Kaboomba</author>
      <guid isPermaLink="true">https://bignerdranch.tistory.com/73</guid>
      <comments>https://bignerdranch.tistory.com/73#entry73comment</comments>
      <pubDate>Sun, 13 Mar 2022 09:14:00 +0900</pubDate>
    </item>
    <item>
      <title>Kotlin's Result sealed class</title>
      <link>https://bignerdranch.tistory.com/71</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;It's cleaner and easier to handle not to throw Exception often in Kotlin. Particulary when we call Http call, we are like to use Coroutines. Coroutine has complicating cancellation and exception handling mechanism and it might not work well in caese if you use the same apporach to Java by throwing Exceptoin. Coroutine propagates Cancellation Exception to top most coroutine context and this makes error and cancellation handling not intuitive. Good option is that not throwing exception and we can wrap it in Result class instead.&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;Result class became public 1.3 but I didn't release until today. (I really wanted the class public but was not avaiable when I started my project and created my own and forgot about it)&lt;/p&gt;
&lt;pre id=&quot;code_1645128789870&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sealed class Result&amp;lt;out R&amp;gt; {
    data class Success&amp;lt;out T&amp;gt;(val data: T) : Result&amp;lt;T&amp;gt;()
    data class Error(val exception: Exception) : Result&amp;lt;Nothing&amp;gt;()
}

 // https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-result/&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;We can use Result class like below.&lt;/p&gt;
&lt;pre id=&quot;code_1645129184323&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Retrofit
suspend fun getUsers(): List&amp;lt;User&amp;gt;

suspend fun getUsers(): Result&amp;lt;List&amp;lt;User&amp;gt;&amp;gt; {
   try {
      Result.success(api.getUsers)
   } catch(e: Exception) [
      Result.failuer(e)
   }
}

when (val result = getUsers()) {
    is Result.Success&amp;lt;List&amp;lt;User&amp;gt;&amp;gt; -&amp;gt; {}
    else -&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;We will repeat try catch if we need to catch exceptions. Kotlin already has extension function to generalise try catch.&lt;/p&gt;
&lt;pre id=&quot;code_1645129405450&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * Calls the specified function [block] with `this` value as its receiver and returns its encapsulated result if invocation was successful,
 * catching any [Throwable] exception that was thrown from the [block] function execution and encapsulating it as a failure.
 */
@InlineOnly
@SinceKotlin(&quot;1.3&quot;)
public inline fun &amp;lt;T, R&amp;gt; T.runCatching(block: T.() -&amp;gt; R): Result&amp;lt;R&amp;gt; {
    return try {
        Result.success(block())
    } catch (e: Throwable) {
        Result.failure(e)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;I didn't relealised this has one flaw until recently. We captured all exceptions and wrapped in Result. As I mementioned earlier, Coroutines needs to handle cancellation and should propagate CancellationException to parent context. But we are capturing CancellationException as well but not rethrowing.&amp;nbsp; Let's add new extension function.&lt;/p&gt;
&lt;pre id=&quot;code_1645133896244&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public inline fun &amp;lt;T, R&amp;gt; T.runCatchingOrThrowCancellation(block: T.() -&amp;gt; R): Result&amp;lt;R&amp;gt; {
    return try {
        Result.success(block())
    } catch (e: Throwable) {
        if (e is CancellationException) {
           throw e
        }
        Result.failure(e)
    }
}&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;This looks fine but if we have a code to show progress bar while calling runCatching and hide when it's finished. Progressbar won't stop if we encounter CancellationException in the case. So we need to signal about Cancellable to outside.&lt;/p&gt;
&lt;pre id=&quot;code_1645134594238&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;viewModelScope.launch {
    runCatchingOrThrowCancellation { repository.getUsers() }
        .onSuccess { ... }
        .onFailure { ... }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;This looks fine but there might have edge case if we have a code to show progress bar while calling runCatching and hide when it's finished. Progressbar won't stop if we encounter CancellationException in the case. So, if you have this case, you might need to handle it in your ViewModel.&lt;/p&gt;
&lt;pre id=&quot;code_1645214907463&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val handler = CoroutineExceptionHandler { _, exception -&amp;gt; 
    liveData.postValue(LoadingState(isLoading = false)
}

viewModelScope.launch(handler) {
    runCatchingOrThrowCancellation { repository.getUsers() }
        .onSuccess { ... }
        .onFailure { ... }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Kotlin</category>
      <author>Kaboomba</author>
      <guid isPermaLink="true">https://bignerdranch.tistory.com/71</guid>
      <comments>https://bignerdranch.tistory.com/71#entry71comment</comments>
      <pubDate>Fri, 18 Feb 2022 05:15:09 +0900</pubDate>
    </item>
    <item>
      <title>DialogFragment handling from a Fragment using Result API</title>
      <link>https://bignerdranch.tistory.com/69</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Android SDK introduced Result API recently and it looks better than just passing target fragment and get the result in onActivityResult. onActivityResult sounds bit weird when we receive the result from a Fragment.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;This API works with Fragments and we can use it for DialogFragment as well because DialogFragment is also Fragment.&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;We need to register listener to FragmentManager to observe from a Fragment. But when we create an DialogFragment, ChildFragment handles DialogFragment and Fragment and DialogFragment become relation Parent and Child. So, we need to use ChildFragment instead of FragmentManager.&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;First, we need to call setFragmentResultListener before we open DialogFragment. requestKey in the parameter is just a String like a Extra key. We could use this to differentiate request and I will show it later.&lt;/p&gt;
&lt;pre id=&quot;code_1645083345233&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class HomeFragment : Fragment(R.layout.fragment_home)

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

        dialogButton.setOnClickLisetner {
            showMyDialog()
        }
    }   
    
    private fun showMyDialog() {
        childFragmentManager.setFragmentResultListener(
           requestKey,
           viewLifecycleOwner,
           { requestKey, bundle -&amp;gt;
              
           })
        
        // TODO : Open DialogFragment
    }  
}&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;Next, we need to show a DialogFragment. Let's create a very simple one.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1645083689775&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;data class DialogInfo(
    val requestKey: String,
    val title: String,
    val message: String,
    val positiveCaption: String,
    val negativeCaption: String? = null
) : Serializable

class MyDialogFragment : DialogFragment() {

    companion object {
        private const val TAG = &quot;MyDialogFragment&quot;
        const val DIALOG_INFO_KEY = &quot;DialogInfo&quot;

        fun show(fm: FragmentManager, dialogInfo: DialogInfo) {
            val fragment = fm.findFragmentByTag(TAG)
            if (fragment != null) return

            MyDialogFragment().apply {
                arguments = bundleOf(DIALOG_INFO_KEY to dialogInfo)
            }.show(fm, TAG)
        }
    }

    private val dialogInfo: DialogInfo by lazyDialogInfo()
    private lateinit var dialogResult: DialogResult

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

        setCancelable(false)
    }

    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        return MaterialAlertDialogBuilder(requireContext())
            .setTitle(dialogInfo.title(requireContext()))
            .setMessage(dialogInfo.message(requireContext()))
            .setPositiveButton(dialogInfo.positiveCaption) { _, _ -&amp;gt;
                
            }
            .setNegativeButton(dialogInfo.negativeCaption) { _, _ -&amp;gt;
                
            }
            .create()
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;We will pass DialogInfo into MyDialogFragment to control UI elements from outside.&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;Now, we can set result and pass to the previous Fragment.&lt;/p&gt;
&lt;pre id=&quot;code_1645083889897&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sealed class DialogResult {
  object Yes: DialogResult()
  object No: DialogResult()
}

class MyDialogFragment : DialogFragment() {
    
    companion object {
       const val RESULT_KEY = &quot;Result&quot;
       ...
    }

    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        return MaterialAlertDialogBuilder(requireContext())
            .setTitle(dialogInfo.title(requireContext()))
            .setMessage(dialogInfo.message(requireContext()))
            .setPositiveButton(dialogInfo.positiveCaption) { _, _ -&amp;gt;
                setDialogResult(DialogResult.Yes)
            }
            .setNegativeButton(dialogInfo.negativeCaption) { _, _ -&amp;gt;
                setDialogResult(DialogResult.No)
            }
            .create()
    }
    
    private fun setDialogResult(result: DialogResult) {
        parentFragmentManager.setFragmentResult(
            dialogInfo.requestKey,
            bundleOf(RESULT_KEY to result)
        )
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Notice that we call parentFragmentManager when we set the result as I mentioned before Fragment and DialogFrament are parent and child relation. Also, we return dialogInfo.resultKey to differentiate each request.&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;Let's handle the result in HomeFragment.&lt;/p&gt;
&lt;pre id=&quot;code_1645084554516&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class HomeFragment : Fragment(R.layout.fragment_home) {

    companion object {
       private const val MY_DIALOG_REQUEST_KEY = &quot;MyDialogRequest&quot;
    }

  ... 
    
    private fun showMyDialog() {
        childFragmentManager.setFragmentResultListener(
           MY_DIALOG_REQUEST_KEY,
           viewLifecycleOwner,
           { _, bundle -&amp;gt;
              when (val result = bundle.getSerializable(MyDialogFragment.RESULT_KEY) as DialogResult) {
                 DialogResult.Yes -&amp;gt; dialogYesClicked()
                 DialogResult.No -&amp;gt; dialogNoClicked()
              }
           })
        
        val dialogInfo = DialogInfo(
            requestKey = MY_DIALOG_REQUEST_KEY,
            title = &quot;Title&quot;,
            message = &quot;Message&quot;
        )
        MyDialogFragment.show(childFragmentManager, dialogInfo)
    }  
    
    private fun dialogYesClicked() {
    
    }
    
    private fun dialogNoClicked() {
    
    }
}&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;We will remove FragmentResultListener after use for safety.&lt;/p&gt;
&lt;pre id=&quot;code_1645084647965&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class HomeFragment : Fragment(R.layout.fragment_home) {
    private fun showMyDialog() {
        childFragmentManager.setFragmentResultListener(
           MY_DIALOG_REQUEST_KEY,
           viewLifecycleOwner,
           { requestKey, bundle -&amp;gt;
             ...
              
              childFragmentManager.clearFragmentResultListener(requestKey)
           })
        
        ...
    }  
   
}&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;The code written sofar is bit verbose and not a clean from can be reused. Let's make those function as extension function.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1645084858925&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface DialogListener {
   fun onPositive()
   fun onNegative(){}
}

fun Fragment.onDialogResult(requestKey: String, listener: DialogResultListener) {
    childFragmentManager.setFragmentResultListener(
        requestKey,
        viewLifecycleOwner,
        { _, bundle -&amp;gt;
            when (val result = bundle.getDialogResult()) {
                DialogResult.Yes -&amp;gt; listener.onPositive()
                DialogResult.No -&amp;gt; listener.onNegative()
            }

            childFragmentManager.clearFragmentResultListener(requestKey)
        })
}

private fun Bundle.getDialogResult(): DialogResult {
    return getSerializable(MyDialogFragment.RESULT_KEY) as DialogResult
}

fun Fragment.popDialog(dialogInfo: DialogInfo) {
    MyDialogFragment.show(
        childFragmentManager,
        dialogInfo
    )
}&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;Let's replace the existing functions in HomeFragment with extension functions.&lt;/p&gt;
&lt;pre id=&quot;code_1645085031571&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class HomeFragment : Fragment(R.layout.fragment_home) {

    companion object {
       private const val MY_DIALOG_REQUEST_KEY = &quot;MyDialogRequest&quot;
    }

  ... 
    
    private fun showMyDialog() {
        getDialogResult(MY_DIALOG_REQUEST_KEY, object: DialogResultListener) {
           override fun onPositive() {
           
           }
           
           override fun onNegateive() {
           
           }
        })
        
        popDialog(myDialogRequestDialog())
    }  
    
    private fun myDialogRequestDialog(): DialogInfo {
      return DialogInfo(
            requestKey = MY_DIALOG_REQUEST_KEY,
            title = &quot;Title&quot;,
            message = &quot;Message&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;Different approach rather than extension function is protocoal like one.&lt;/p&gt;
&lt;pre id=&quot;code_1645085219695&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface DialogProtocol {
   val owner: Fragment
   
   fun onDialogResult(requestKey: String, listener: DialogResultListener) {
        sender.childFragmentManager.setFragmentResultListener(
           requestKey,
           owner.viewLifecycleOwner,
           { _, bundle -&amp;gt;
              when (val result = bundle.getDialogResult()) {
                DialogResult.Yes -&amp;gt; listener.onPositive()
                DialogResult.No -&amp;gt; listener.onNegative()
              }

              owner.childFragmentManager.clearFragmentResultListener(requestKey)
        })
        
   fun popDialog(dialogInfo: DialogInfo) {
      MyDialogFragment.show(
        owner.childFragmentManager,
        dialogInfo
      )
   } 
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1645085272361&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class HomeFragment : Fragment(R.layout.fragment_home), DialogProtocol {

    companion object {
       private const val MY_DIALOG_REQUEST_KEY = &quot;MyDialogRequest&quot;
    }

    override val owner: Fragment get() = this
  ... 
    
    private fun showMyDialog() {
        getDialogResult(MY_DIALOG_REQUEST_KEY, object: DialogResultListener) {
           override fun onPositive() {
           
           }
           
           override fun onNegateive() {
           
           }
        })
        
        popDialog(myDialogRequestDialog())
    }  
    
    private fun myDialogRequestDialog(): DialogInfo {
      return DialogInfo(
            requestKey = MY_DIALOG_REQUEST_KEY,
            title = &quot;Title&quot;,
            message = &quot;Message&quot;
        )
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Extension and Protocol approach are very similar and one big difference is extension function will be visible all Framgnet but Protocol visible to only implement it.&lt;/p&gt;</description>
      <category>Android</category>
      <author>Kaboomba</author>
      <guid isPermaLink="true">https://bignerdranch.tistory.com/69</guid>
      <comments>https://bignerdranch.tistory.com/69#entry69comment</comments>
      <pubDate>Thu, 17 Feb 2022 17:08:57 +0900</pubDate>
    </item>
    <item>
      <title>Android, how to use String Resource in an abstract way</title>
      <link>https://bignerdranch.tistory.com/67</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;We have use cases when we have to pass String message from ViewModel to View often. In Android,&amp;nbsp; the system support String resources and it would be nice to use String resource instead of hard coded strings whenever possible. You might have had some situations you need to pass String resources from your ViewModel to activity or fragment but you don't want to pass Context to your ViewModel. You might have wrapper of String resources in use this in your ViewModel.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1644758061927&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface StringResource {
    fun getString(resId: Int): CharSequence
}

class StringResourceImpl(private val context: Context) {
   override fun getString(resId: Int): CharSequence {
      return context.getString(resId)
   }
}

class MyViewModel(
   private val stringResource: StringResource
) {
   ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;StringResourceImpl class still has Context reference of your View and extracting specific strings from resources is not ViewModel's job. It would be enough to have resource id in ViewModel.&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;I have slightly different option for the case. We don't hold Context reference when we instantiate the wrapper but we can use context when we actually get a string using context parameter.&amp;nbsp; Here is my implementation:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1644758438756&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sealed class Txt {
   abstract fun getString(context: Context): CharSequence()
   class R(val resId: Int, vararg args: Txt): Txt() {
      private val fmtArgs = args

      override fun getString(context: Context): CharSequence {
        if (fmtArgs.isNotEmpty()) {
            val strArgs = fmtArgs.map { txt -&amp;gt;
                txt.getString(context)
            }.toTypedArray()
            return context.getString(resId, *strArgs)
        }

        return context.getString(resId)
      }
   }
   
   class S(private val s: CharSequence) : Txt {
     override fun getString(context: Context): CharSequence {
        return s
     }
   }
}&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;StringResources has two different types, CharSequence and Resource ID.&amp;nbsp; We can wrap it using sealed class and we will use Context only when we call getString function from a View. Also, we can pass Txt instance into Txt.R class and extract strings by calling Context.getString(resId, fmtArgs).&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;We could use Txt class like below.&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;strings.xml:&lt;/p&gt;
&lt;pre id=&quot;code_1644758695522&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;resources&amp;gt;
   &amp;lt;string name=&quot;error_guide_message&quot;&amp;gt;Something went wrong. Please contact call %1$s for further help.&amp;lt;/string&amp;gt;
   &amp;lt;string name=&quot;call_center_phone_no&quot;&amp;gt;123&amp;ndash;4567&amp;lt;/string&amp;gt;
&amp;lt;/resources&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1644758791390&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;object CommonStr {
    val callCenterPhoneNo by lazy { Txt.R(R.string.call_center_phone_no) }
}

val errorGuideMessage = Txt.R(R.string.error_guide_message, CommonStr.callCenterPhoneNo)
errorGuideMessage.getString(requireContext())&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;output:&lt;br /&gt;Something&amp;nbsp;went&amp;nbsp;wrong.&amp;nbsp;Please&amp;nbsp;contact&amp;nbsp;call&amp;nbsp;123&amp;ndash;4567&amp;nbsp;for&amp;nbsp;further&amp;nbsp;help.&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;We could take the code bit further using inline class to improve performance. I am not sure how much I would gain but I believe there would be some performance benefit because most of application use internalisation strings a lot.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Here is inline class version.&lt;/p&gt;
&lt;pre id=&quot;code_1645092008413&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sealed interface Txt : Serializable {
    fun getString(context: Context): CharSequence
}

@JvmInline
value class Str(private val s: CharSequence) : Txt {
    override fun getString(context: Context): CharSequence {
        return s
    }
}

@JvmInline
value class StrR(private val resId: Int) : Txt {
    override fun getString(context: Context): CharSequence {
        return context.getString(resId)
    }
}

class StrRArgs(private val resId: Int, private val args: List&amp;lt;Txt&amp;gt;) : Txt {
    override fun getString(context: Context): CharSequence {
        if (args.isNotEmpty()) {
            val strArgs = args.map { txt -&amp;gt;
                txt.getString(context)
            }.toTypedArray()
            return context.getString(resId, *strArgs)
        }

        return context.getString(resId)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;I splited Res and ResArgs class and ResArgs cannot become inline class because it has two constructor parameters. But Str and Res are inline classes and ResArgs also will have some benefit from this change.&lt;/p&gt;</description>
      <category>Android</category>
      <author>Kaboomba</author>
      <guid isPermaLink="true">https://bignerdranch.tistory.com/67</guid>
      <comments>https://bignerdranch.tistory.com/67#entry67comment</comments>
      <pubDate>Sun, 13 Feb 2022 22:26:49 +0900</pubDate>
    </item>
    <item>
      <title>Take favour read-time convenience over write-time convenience</title>
      <link>https://bignerdranch.tistory.com/66</link>
      <description>&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Favor read-time convenience to write-time convenience. Code is read far more times than it's written, even during initial development. Favoring a technique that speeds write-time convenience at the expense of read-time convenience is a false economy. This is especially applicable to creation of class interfaces. Even if a routine doesn't quite fit the interface's abstraction, sometimes it's tempting to add a routine to an interface that would be convenient for the particular client of a class that you're working on at the time. But adding that routine is the first step down a slippery slope, and it's better not to take even the first step.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Code Complete, Steve McConnell&lt;/span&gt;&lt;/p&gt;</description>
      <category>Object Oriented Programming/Coding Practice 101</category>
      <author>Kaboomba</author>
      <guid isPermaLink="true">https://bignerdranch.tistory.com/66</guid>
      <comments>https://bignerdranch.tistory.com/66#entry66comment</comments>
      <pubDate>Mon, 7 Feb 2022 05:06:37 +0900</pubDate>
    </item>
    <item>
      <title>Fragment에서 ViewBinding 메모리누수 방지하기</title>
      <link>https://bignerdranch.tistory.com/65</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Kotlin사용자라면 Sythetic pulgin이 deprecated되고 ViewBinding(구글의 권장사항)으로 마이그레이션 해야된다는 걸 알고 있을 것이다. 그런데 ViewBinding을 프레그먼트에서 사용하게 될 경우 Memory leak과 관련된 이슈가 있는 것도 아마 알 것이다. (구글 문서에 나와있으므로)&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;a href=&quot;https://developer.android.com/topic/libraries/view-binding&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://developer.android.com/topic/libraries/view-binding&lt;/a&gt;)&lt;/p&gt;
&lt;pre id=&quot;code_1627630776014&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private var _binding: ResultProfileBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!

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

override fun onDestroyView() {
    super.onDestroyView()
    _binding = null
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 이렇게 중요노트가 달려있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Note:&lt;/b&gt;&lt;span style=&quot;color: #01579b;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;Fragments outlive their views. Make sure you clean up any references to the binding class instance in the fragment's&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://developer.android.com/reference/kotlin/androidx/fragment/app/Fragment#ondestroyview&quot;&gt;onDestroyView()&lt;/a&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;method.&lt;/span&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프레그먼트에 있는 뷰들이 원래 보다 오래 살아있을 수 있기 때문에 이런 증상이 발생한다는 것이다. 이건 구글이 프레그먼트를 리팩토링하면서 기본적으로 프레그먼트에 있는 뷰를 프레그먼트가 종료되어도 살수 있도록 변경했기 때문이다. 그렇게 때문에 GC가 메모리를 회수하려고 해도 binding이 레퍼런스를 계속 가지고 있기 때문에 회수하지 못하고 메모리 누수가 나게 되는 것이다. 왜 이렇게 했는지는 정확하게 이해하지 못하겠지만 말이다.&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;그런데, 프레그먼트가 몇 개 안되는 앱이라면 그냥 구글의 코드처럼 따라하면 될 것이다. 그러나 사이즈가 큰 앱같은 경우는 위와 같은 코드는 보일러플레이트 코드이고, 실수로 onDestoryView에 널을 세팅하는 것을 잊어버릴 수가 있다. 그러면 이 문제를 해결하려면 어떻게 하는 것이 좋을까?&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;이미 전 세계의 똑똑한 개발자들이 이에 대한 여러가지 해결방법을 제시하고 있는데, Property Delegation 을 이용하는 방법이 그 중의 하나이다. (&lt;a href=&quot;https://zhuinden.medium.com/simple-one-liner-viewbinding-in-fragments-and-activities-with-kotlin-961430c6c07c&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://zhuinden.medium.com/simple-one-liner-viewbinding-in-fragments-and-activities-with-kotlin-961430c6c07c&lt;/a&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;소스코드를 전체 보기 전에 프레그먼트에서 우리가 원하는 모습의 코드를 보기로 하자. 우리는 이 코드가 아주 짧았으면 좋겠고, 자동으로 알아서 프레그먼트가 onDestory될 때 메모리를 해제해줫으면 좋겠다. 따라서 아래와 같은 모습으로 사용하면 좋을 것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1627631512640&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class HomdFragment: Fragment() {
   
   private val binding by viewBidning { FragmentHomeBinding.bind(requiredView()) }

}&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;위처럼, 사용할 수 있다면 정말 깔끔할 것 같다. 위에서 by는 Property Delegation 키워드이다.&amp;nbsp; by 뒤에 나오는 코드는 ReadOnlyProperty 또는 ReaWriteProperty를 구현해 주어야 한다. Property Delegate 는 해당 Property를 읽거나 쓰는 코드를 다른 클래스 등이 처리하도록 하는 것이다. 우리도 이 테크닉을 적용하여 View binding을 우리가 직접하지 않고 다른 클래스에 맡기도록 하자. 그런데 위처럼 Property Delegation을 하면서 nCreateView에서 바인딩하는 코드를 사용한다면 위와 같이 만들 필요가 전혀 없다. 그럼 어떻게 하면 onCreateView 에서 레이아웃을 inflate 하는 부분이 자동으로 처리되게 할 수 있을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실마리는, 프레그먼트 클래스의 생성자에 있다. 프레그먼트 클래스를 들여다 보면, 아래와 같은 생성자를 볼 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1627631734161&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@ContentView
public Fragment(@LayoutRes int contentLayoutId) {
    this();
    mContentLayoutId = contentLayoutId;
}
    
@MainThread
@Nullable
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
            @Nullable Bundle savedInstanceState) {
    if (mContentLayoutId != 0) {
        return inflater.inflate(mContentLayoutId, container, false);
    }
    return null;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, Layout ID를 넘겨주면 onCreateView에서 자동으로 inflate를 해주게 되는 것이다. 따라서 우리가 호출하고 픈 모양의 Property호출은&lt;/p&gt;
&lt;pre id=&quot;code_1627631939381&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class HomeFragment: Fragment(R.layout.fragment_home) {
   
   private val binding by viewBidning { FragmentHomeBinding.bind(requiredView()) }

}&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;여기서 viewBinding을 보면 일단은 프레그먼트에 속하는 함수이고 일기만 할 것이므로 ReadOnlyProperty를 리턴하도록 해야한다는 것을 알 수 있다. 그리고 viewBinding은 매개가 하나인데, bind 호출해서 requiredView()를 바인딩 해주고 있다. 모든 Binding class들은 ViewBinding interface를 구현하고 있고 Bind(View) 함수를 가지고 있다. 아래와 같은 모양이 될 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1627633326629&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;inding&amp;gt; Fragment.viewBinding(onBind: (View) -&amp;gt; T) = FragmentViewBindingDelegate&amp;lt;T&amp;gt;(this, onBind)&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;이제 FragmentViewBindingDelegate를 구현해 보자. 먼저 depency에 아래 라이브러들이 필요할 것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1627633475477&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;implementation &quot;androidx.lifecycle:lifecycle-runtime-ktx:2.3.1&quot;
implementation &quot;androidx.lifecycle:lifecycle-common-java8:2.3.1&quot;
implementation &quot;androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구현된 코드이다.&lt;/p&gt;
&lt;pre id=&quot;code_1627633373192&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;FragmentViewBindingDelegate&amp;lt;BINDING: ViewBinding&amp;gt;(
    private val fragment: Fragment,
    private val onBind: (View) -&amp;gt; BINDING
): ReadOnlyProperty&amp;lt;Fragment, BINDING&amp;gt; {
    private var binding: BINDING? = null

    private val lifecycle get() = fragment.viewLifecycleOwner.lifecycle

    init {
        fragment.lifecycle.addObserver(object: DefaultLifecycleObserver {
            val viewLifecycleOwnerLiveDataObserver = Observer&amp;lt;LifecycleOwner?&amp;gt; { lifecycleOwner -&amp;gt;
                val viewLifecycleOwner = lifecycleOwner ?: return@Observer

                viewLifecycleOwner.lifecycle.addObserver(object: DefaultLifecycleObserver {
                    override fun onDestroy(owner: LifecycleOwner) {
                        binding = null
                    }
                })
            }

            override fun onCreate(owner: LifecycleOwner) {
                fragment.viewLifecycleOwnerLiveData.observeForever(viewLifecycleOwnerLiveDataObserver)
            }

            override fun onDestroy(owner: LifecycleOwner) {
                fragment.viewLifecycleOwnerLiveData.removeObserver(viewLifecycleOwnerLiveDataObserver)
            }
        })
    }

    override fun getValue(thisRef: Fragment, property: KProperty&amp;lt;*&amp;gt;): BINDING {
        val localBinding = binding
        if (localBinding != null) {
            return localBinding
        }

        if (!lifecycle.currentState.isAtLeast(Lifecycle.State.INITIALIZED)) {
            throw IllegalStateException(&quot;Should not attempt to get bindings when Fragment views are destroyed.&quot;)
        }

        return onBind(thisRef.requireView()).also {
            binding = it
        }
    }
}&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; 프레그먼트의 Lifecycle에 Observer를 등록해서 뷰가 inflate되었다면, 뷰를 바인딩하는 부분과&amp;nbsp; Destory될 때 자동으로 bindig변수가 널이 되게 하는 것이다. 그리고 viewLifecycleOwnerLiveData를 observe함으로써 Fragment가 onCreate되었다가 바로 onDestory되는 경우가 생길 때 onStart나 onStop이 호출되지 않기 때문에 생길 수 있는 문제를 방지하는 코드가 들어간 점이다. 참고로 LiveData.observeForever는 observe가 프레임웤이 알아서 observer를 관리해주는 대신 observeForever는 항상 active상태가 되고 매뉴얼로 제거해주어야 한다. 테스트 코드나 위의 경우에는&amp;nbsp; observeForever가 적절한 사용이 될 것이다.&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;</description>
      <category>Android</category>
      <author>Kaboomba</author>
      <guid isPermaLink="true">https://bignerdranch.tistory.com/65</guid>
      <comments>https://bignerdranch.tistory.com/65#entry65comment</comments>
      <pubDate>Fri, 30 Jul 2021 17:34:31 +0900</pubDate>
    </item>
  </channel>
</rss>