<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Archive</title>
    <link>https://btfnswt.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Tue, 21 Jul 2026 02:45:14 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>yeonkims</managingEditor>
    <image>
      <title>Archive</title>
      <url>https://tistory1.daumcdn.net/tistory/2892553/attach/815db15badc74a1dad086c04dbd57805</url>
      <link>https://btfnswt.tistory.com</link>
    </image>
    <item>
      <title>onCreateView와 onViewCreated의 차이</title>
      <link>https://btfnswt.tistory.com/219</link>
      <description>&lt;h2 data-end=&quot;76&quot; data-start=&quot;52&quot; data-ke-size=&quot;size26&quot;&gt;onCreateView()&lt;/h2&gt;
&lt;blockquote data-end=&quot;137&quot; data-start=&quot;77&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-end=&quot;137&quot; data-start=&quot;79&quot; data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;&lt;b&gt;Fragment의 XML을 메모리에 올리고(binding)&lt;/b&gt;, root view를 반환하는 단계&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;281&quot; data-start=&quot;139&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;174&quot; data-start=&quot;139&quot;&gt;역할: &lt;b&gt;레이아웃 inflate + binding 연결&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;246&quot; data-start=&quot;175&quot;&gt;이 시점에는 뷰가 &amp;ldquo;만들어지는 중&amp;rdquo;이라, 아직 Activity에 완전히 붙지 않았고 화면 표시도 안 된 상태이다.&lt;/li&gt;
&lt;li data-end=&quot;281&quot; data-start=&quot;247&quot;&gt;그래서 여기서는 &lt;b&gt;최소한의 일만 하는 게 원칙&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1761990951489&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;override fun onCreateView(
    inflater: LayoutInflater, container: ViewGroup?,
    savedInstanceState: Bundle?
): View {
    _binding = FragmentSampleBinding.inflate(inflater, container, false)
    return binding.root
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;h2 data-end=&quot;549&quot; data-start=&quot;524&quot; data-ke-size=&quot;size26&quot;&gt;onViewCreated()&lt;/h2&gt;
&lt;blockquote data-end=&quot;589&quot; data-start=&quot;550&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-end=&quot;589&quot; data-start=&quot;552&quot; data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;&lt;b&gt;뷰가 다 만들어졌으니, 이제 조립하고 동작을 붙이는 단계&lt;/b&gt;&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;769&quot; data-start=&quot;591&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;647&quot; data-start=&quot;591&quot;&gt;역할: &lt;b&gt;UI 초기화, 리스너 연결, Adapter 세팅, LiveData/Flow 관찰 등&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;769&quot; data-start=&quot;648&quot;&gt;이제 binding.root가 Activity에 attach되었기 때문에&lt;br /&gt;requireContext(), viewLifecycleOwner, binding.xxx 전부 안전하게 쓸 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1761990973204&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    setupRecyclerView()
    setupSpinners()
    setButtonListeners()
    observeViewModel()
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-end=&quot;1021&quot; data-start=&quot;1006&quot; data-ke-size=&quot;size26&quot;&gt;요약&lt;/h2&gt;
&lt;div&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-end=&quot;1288&quot; data-start=&quot;1023&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;메서드&lt;/td&gt;
&lt;td&gt;시점&lt;/td&gt;
&lt;td&gt;해야 할 일&lt;/td&gt;
&lt;td&gt;하지 말아야 할 일&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;1211&quot; data-start=&quot;1110&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;1127&quot; data-start=&quot;1110&quot;&gt;onCreateView&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;1139&quot; data-start=&quot;1127&quot;&gt;뷰 &amp;ldquo;만드는 중&amp;rdquo;&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;1185&quot; data-start=&quot;1139&quot;&gt;XML &amp;harr; Fragment 연결 (inflate, binding 생성)&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;1211&quot; data-start=&quot;1185&quot;&gt;리스너/Flow 수집/Adapter 연결&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;1288&quot; data-start=&quot;1212&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;1230&quot; data-start=&quot;1212&quot;&gt;onViewCreated&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;1243&quot; data-start=&quot;1230&quot;&gt;뷰 &amp;ldquo;다 만들어짐&amp;rdquo;&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;1283&quot; data-start=&quot;1243&quot;&gt;리스너, Adapter, Flow, Livedata 등 UI 초기화&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;1288&quot; data-start=&quot;1283&quot;&gt;&amp;mdash;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1307&quot; data-start=&quot;1295&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1307&quot; data-start=&quot;1295&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;한 줄 정리&lt;/b&gt;&lt;/p&gt;
&lt;blockquote data-end=&quot;1390&quot; data-start=&quot;1308&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-end=&quot;1390&quot; data-start=&quot;1310&quot; data-ke-size=&quot;size16&quot;&gt;onCreateView에서는 &amp;ldquo;뷰를 만들기만&amp;rdquo; 하고,&lt;br /&gt;onViewCreated에서 &amp;ldquo;그 뷰를 세팅하고 움직이게&amp;rdquo; 한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-is-only-node=&quot;&quot; data-is-last-node=&quot;&quot; data-end=&quot;1497&quot; data-start=&quot;1392&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-is-only-node=&quot;&quot; data-is-last-node=&quot;&quot; data-end=&quot;1497&quot; data-start=&quot;1392&quot; data-ke-size=&quot;size16&quot;&gt;이것이 &lt;b&gt;Fragment의 생명주기에서 가장 중요한 패턴&lt;/b&gt;이며,&lt;br /&gt;실무에서도 &amp;ldquo;inflate는 onCreateView, 나머지는 onViewCreated&amp;rdquo;가 표준이다.&lt;/p&gt;</description>
      <category>[앱 개발]/Android</category>
      <author>yeonkims</author>
      <guid isPermaLink="true">https://btfnswt.tistory.com/219</guid>
      <comments>https://btfnswt.tistory.com/219#entry219comment</comments>
      <pubDate>Sat, 1 Nov 2025 18:58:31 +0900</pubDate>
    </item>
    <item>
      <title>[Android] 최신 안드로이드 MVVM + StateFlow 표준 패턴</title>
      <link>https://btfnswt.tistory.com/218</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Repository &amp;rarr; ViewModel(StateFlow) &amp;rarr; UI(Fragment/Compose) 두 가지 UI 버전 예시에 포함됨.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 data-end=&quot;448&quot; data-start=&quot;405&quot;&gt;0) 모델 &amp;amp; UI 상태&lt;/h1&gt;
&lt;pre id=&quot;code_1761990734373&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// domain/model
data class User(val id: String, val name: String)

// presentation/state
data class UserUiState(
    val loading: Boolean = false,
    val data: List&amp;lt;User&amp;gt; = emptyList(),
    val error: String? = null
)&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;h1 data-end=&quot;448&quot; data-start=&quot;405&quot;&gt;1) DataSource &amp;amp; Repository (Flow로 데이터 제공)&lt;/h1&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1761990762088&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// data/remote
interface UserRemoteDataSource {
    suspend fun fetchUsers(): List&amp;lt;User&amp;gt;
}

// data/repo
class UserRepository(
    private val remote: UserRemoteDataSource
) {
    // Flow로 비동기 데이터 스트림 제공
    fun users(): Flow&amp;lt;Result&amp;lt;List&amp;lt;User&amp;gt;&amp;gt;&amp;gt; = flow {
        emit(Result.success(remote.fetchUsers()))
    }.catch { e -&amp;gt;
        emit(Result.failure(e))
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;blockquote data-end=&quot;896&quot; data-start=&quot;829&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-end=&quot;896&quot; data-start=&quot;831&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;실제론 Room/Network를 combine하거나 emitAll(roomDao.flow())처럼 섞기도 함.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1 data-end=&quot;943&quot; data-start=&quot;903&quot;&gt;2) ViewModel (Flow &amp;rarr; StateFlow 변환: 핵심)&lt;/h1&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1761990782758&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class UserViewModel(
    private val repo: UserRepository
) : ViewModel() {

    // Repository의 Flow를 UI가 쓰기 좋은 StateFlow&amp;lt;UserUiState&amp;gt;로 변환
    val uiState: StateFlow&amp;lt;UserUiState&amp;gt; =
        repo.users()
            .map { result -&amp;gt;
                result.fold(
                    onSuccess = { UserUiState(loading = false, data = it, error = null) },
                    onFailure = { UserUiState(loading = false, data = emptyList(), error = it.message) }
                )
            }
            // 로딩 신호를 주고 싶다면 시작 전에 한 번 emit
            .onStart { emit(UserUiState(loading = true)) }
            // 같은 상태 연속 방출 방지
            .distinctUntilChanged()
            // **표준 패턴:** ViewModel 스코프 + WhileSubscribed(유예 5초) + 초기값
            .stateIn(
                scope = viewModelScope,
                started = SharingStarted.WhileSubscribed(5_000),
                initialValue = UserUiState(loading = true)
            )

    // 예: 새로고침 트리거
    fun refresh() {
        viewModelScope.launch {
            // 필요시 repo 호출 로직 추가
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 data-end=&quot;2061&quot; data-start=&quot;2016&quot;&gt;3A) UI: Fragment + ViewBinding (생명주기 안전 수집)&lt;/h1&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1761990800255&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class UserFragment : Fragment(R.layout.fragment_user) {

    private var _binding: FragmentUserBinding? = null
    private val binding get() = _binding!!
    private val viewModel: UserViewModel by viewModels()

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

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

        // 어댑터/리스너 세팅 등 UI 초기화&amp;hellip;
        setupRecycler()

        // **표준 수집 패턴**
        viewLifecycleOwner.lifecycleScope.launch {
            viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
                viewModel.uiState.collect { state -&amp;gt;
                    binding.progressBar.isVisible = state.loading
                    binding.errorText.isVisible = state.error != null
                    binding.errorText.text = state.error ?: &quot;&quot;

                    submitUsers(state.data)
                }
            }
        }

        binding.refreshBtn.setOnClickListener { viewModel.refresh() }
    }

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

    private fun setupRecycler() { /* ... */ }
    private fun submitUsers(users: List&amp;lt;User&amp;gt;) { /* adapter.submitList(users) ... */ }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;h1 data-end=&quot;3562&quot; data-start=&quot;3520&quot;&gt;3B) UI: Jetpack Compose (collectAsState)&lt;/h1&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1761990814304&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun UserScreen(viewModel: UserViewModel = hiltViewModel()) {
    val state by viewModel.uiState.collectAsState() // StateFlow &amp;rarr; Compose State

    when {
        state.loading -&amp;gt; CircularProgressIndicator()
        state.error != null -&amp;gt; ErrorBox(state.error!!) { viewModel.refresh() }
        else -&amp;gt; UserList(state.data)
    }
}

@Composable
fun ErrorBox(msg: String, onRetry: () -&amp;gt; Unit) {
    Column {
        Text(text = msg)
        Button(onClick = onRetry) { Text(&quot;Retry&quot;) }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;h1 data-end=&quot;4118&quot; data-start=&quot;4087&quot;&gt;4) 왜 이게 &amp;ldquo;현업 표준&amp;rdquo;인가 (핵심 포인트 요약)&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;4498&quot; data-start=&quot;4120&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;4163&quot; data-start=&quot;4120&quot;&gt;&lt;b&gt;Repository는 Flow&lt;/b&gt;로 데이터 발행 &amp;rarr; 테스트/교체 쉬움.&lt;/li&gt;
&lt;li data-end=&quot;4351&quot; data-start=&quot;4164&quot;&gt;&lt;b&gt;ViewModel에서 map/onStart/distinctUntilChanged/stateIn&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;4351&quot; data-start=&quot;4227&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;4316&quot; data-start=&quot;4227&quot;&gt;stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), initial) = &lt;b&gt;정석 패턴&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;4351&quot; data-start=&quot;4319&quot;&gt;&amp;ldquo;구독 있을 때만 실행 + 5초 유예 + 초기값 보유&amp;rdquo;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;4446&quot; data-start=&quot;4352&quot;&gt;**Fragment는 repeatOnLifecycle(STARTED)**로 &lt;b&gt;생명주기 안전 수집&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;4446&quot; data-start=&quot;4415&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;4446&quot; data-start=&quot;4415&quot;&gt;View가 사라지면 자동 중단, 다시 보이면 재시작.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;4498&quot; data-start=&quot;4447&quot;&gt;&lt;b&gt;Compose는 collectAsState()&lt;/b&gt; 하나로 끝 &amp;rarr; 선언형 UI와 찰떡.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 data-end=&quot;4521&quot; data-start=&quot;4505&quot;&gt;5) 자주 하는 확장/추가&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;4801&quot; data-start=&quot;4523&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;4591&quot; data-start=&quot;4523&quot;&gt;&lt;b&gt;UIState를 sealed class&lt;/b&gt;로 만들고 Loading/Success/Error로 구분해도 좋음.&lt;/li&gt;
&lt;li data-end=&quot;4642&quot; data-start=&quot;4592&quot;&gt;이벤트(토스트/네비게이션)는 SharedFlow(replay=0)로 별도 관리.&lt;/li&gt;
&lt;li data-end=&quot;4718&quot; data-start=&quot;4643&quot;&gt;Repository에서 Room.flow()와 네트워크 flow를 combine/flatMapLatest로 합침.&lt;/li&gt;
&lt;li data-end=&quot;4801&quot; data-start=&quot;4719&quot;&gt;WhileSubscribed(5_000)의 5초는 팀 컨벤션에 따라 1~5초. 상수로 빼두면 더 깔끔 (GRACE_MS = 5_000).&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>[앱 개발]/Android</category>
      <author>yeonkims</author>
      <guid isPermaLink="true">https://btfnswt.tistory.com/218</guid>
      <comments>https://btfnswt.tistory.com/218#entry218comment</comments>
      <pubDate>Sat, 1 Nov 2025 18:54:34 +0900</pubDate>
    </item>
    <item>
      <title>[Android] Flow에서의 map</title>
      <link>https://btfnswt.tistory.com/217</link>
      <description>&lt;h2 data-end=&quot;982&quot; data-start=&quot;954&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Flow에서의 map의 의미&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프래그먼트 쪽 collect { show -&amp;gt; ... }는 &lt;b&gt;showPickNumberSpinner 값이 바뀔 때마다&lt;/b&gt; 람다를 실행해서 스피너 isVisible을 토글한다.&lt;/p&gt;
&lt;p data-end=&quot;336&quot; data-start=&quot;112&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;mode가 바뀌면 &amp;rarr; 그 변화를 map { ... }이 반영 &amp;rarr; showPickNumberSpinner가 자동으로 바뀐다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1761990508843&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;(1) _mode (MutableStateFlow&amp;lt;Mode&amp;gt;)  &amp;larr; 여기 값을 바꿈 (업스트림)
        │ emits
        ▼
(2) mode: StateFlow&amp;lt;Mode&amp;gt; (= _mode.asStateFlow())
        │ map { it != RANK &amp;amp;&amp;amp; it != DICE }
        ▼
(3) showPickNumberSpinner: StateFlow&amp;lt;Boolean&amp;gt;
        │ collect (Fragment에서 수집)
        ▼
(4) UI 업데이트 (Spinner isVisible 토글)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;713&quot; data-start=&quot;678&quot;&gt;(1)에서 &lt;b&gt;실제로 값이 바뀌는 지점&lt;/b&gt;이 필요합니다.&lt;/li&gt;
&lt;li data-end=&quot;783&quot; data-start=&quot;714&quot;&gt;(2) &amp;rarr; (3)은 &lt;b&gt;파생&lt;/b&gt;: mode가 emit될 때마다 map이 실행되어 Boolean으로 변환됩니다.&lt;/li&gt;
&lt;li data-end=&quot;830&quot; data-start=&quot;784&quot;&gt;(4)는 프래그먼트가 수집하고 있으니, 값이 바뀔 때마다 UI가 갱신됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 보통 ViewModel 안에 이런 코드가 있어야 한다:&lt;/p&gt;
&lt;pre id=&quot;code_1761990570569&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;_mode.value = newMode&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;emit 함수를 직접 사용하지 않더라도,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MutableStateFlow에 value를 대입하는 순간 (값을 갱신하는 순간)이 곧 emit 이다.&lt;/p&gt;
&lt;pre id=&quot;code_1761990272997&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;_mode.value = newMode   // &amp;larr; 이 한 줄이 'emit(newMode)'와 같은 효과&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;두 방식의 비교&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1761990319264&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private val _mode = MutableStateFlow(Mode.PICK)
val mode: StateFlow&amp;lt;Mode&amp;gt; = _mode.asStateFlow()

fun updateModeImmediate(newMode: Mode) {
    // 바로 변경 &amp;mdash; 일반 함수에서도 가능
    _mode.value = newMode
}

suspend fun updateModeSuspend(newMode: Mode) {
    // 코루틴 내부에서만 가능 (예: repository 콜백 안)
    _mode.emit(newMode)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-end=&quot;1146&quot; data-start=&quot;1127&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;실제 현업에서는?&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1301&quot; data-start=&quot;1148&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1228&quot; data-start=&quot;1148&quot;&gt;대부분의 &lt;b&gt;UI 상태(StateFlow)&lt;/b&gt; 는 메인스레드에서 즉시 변경되니까 value = 방식을 거의 표준으로 쓴다.&lt;/li&gt;
&lt;li data-end=&quot;1301&quot; data-start=&quot;1230&quot;&gt;반면, &lt;b&gt;suspend 함수나 flow 연산자 내부에서 emit해야 할 때&lt;/b&gt;는 emit()을 사용한다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>[앱 개발]/Android</category>
      <author>yeonkims</author>
      <guid isPermaLink="true">https://btfnswt.tistory.com/217</guid>
      <comments>https://btfnswt.tistory.com/217#entry217comment</comments>
      <pubDate>Sat, 1 Nov 2025 18:49:44 +0900</pubDate>
    </item>
    <item>
      <title>[Android] 자주 쓰이는 3가지 오버로딩 map 함수들</title>
      <link>https://btfnswt.tistory.com/216</link>
      <description>&lt;h3 data-end=&quot;567&quot; data-start=&quot;539&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. List / Iterable용 map&lt;/b&gt;&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1761989773687&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// kotlinx.coroutines.flow.FlowKt__TransformKt
public fun &amp;lt;T, R&amp;gt; Flow&amp;lt;T&amp;gt;.map(transform: suspend (T) -&amp;gt; R): Flow&amp;lt;R&amp;gt; =
    unsafeTransform { value -&amp;gt; emit(transform(value)) }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;893&quot; data-start=&quot;853&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;즉시 계산&lt;/b&gt;, 자료구조를 바꿔서 새 컬렉션을 리턴&lt;/p&gt;
&lt;p data-end=&quot;893&quot; data-start=&quot;853&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;921&quot; data-start=&quot;900&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. Sequence용 map&lt;/b&gt;&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1761989795637&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// kotlin.sequences.Sequences.kt
public fun &amp;lt;T, R&amp;gt; Sequence&amp;lt;T&amp;gt;.map(transform: (T) -&amp;gt; R): Sequence&amp;lt;R&amp;gt; {
    return TransformingSequence(this, transform)
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1168&quot; data-start=&quot;1090&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;지연 계산&lt;/b&gt;, 필요할 때만 계산&lt;br /&gt;TransformingSequence는 실제로 iterator()가 돌기 전까지 아무 일도 안 함.&lt;/p&gt;
&lt;p data-end=&quot;1168&quot; data-start=&quot;1090&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;1192&quot; data-start=&quot;1175&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. Flow용 map&lt;/b&gt;&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1761989812869&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// kotlinx.coroutines.flow.FlowKt__TransformKt
public fun &amp;lt;T, R&amp;gt; Flow&amp;lt;T&amp;gt;.map(transform: suspend (T) -&amp;gt; R): Flow&amp;lt;R&amp;gt; =
    unsafeTransform { value -&amp;gt; emit(transform(value)) }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1479&quot; data-start=&quot;1380&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;비동기 반응&lt;/b&gt;, 값이 들어올 때마다 emit&lt;br /&gt;collect가 시작되면, upstream이 emit할 때마다 transform(value) 실행 &amp;rarr; downstream에 emit.&lt;/p&gt;</description>
      <category>[앱 개발]/Android</category>
      <author>yeonkims</author>
      <guid isPermaLink="true">https://btfnswt.tistory.com/216</guid>
      <comments>https://btfnswt.tistory.com/216#entry216comment</comments>
      <pubDate>Sat, 1 Nov 2025 18:38:03 +0900</pubDate>
    </item>
    <item>
      <title>실전에서 if-else 대신 enum + when 구조를 써야하는 이유</title>
      <link>https://btfnswt.tistory.com/214</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;FingerPick 업데이트로 새로운 모드를 추가하던 중,&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;새로운 enum 값이 추가되면, else가 있는 경우엔 버그가 숨어들 가능성이 높아지게 되는데,&lt;/p&gt;
&lt;p data-end=&quot;1295&quot; data-start=&quot;1217&quot; data-ke-size=&quot;size16&quot;&gt;else 없는 when을 사용해 두면 컴파일러가 &quot;이 모드 처리 안 했잖아!&quot; 하고 알려주게 되어 버그를 줄일 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;1295&quot; data-start=&quot;1217&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1761907093643&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val pickNumbers = when (mode) {
    Mode.PICK, Mode.RANK -&amp;gt; ...
    Mode.TEAM -&amp;gt; ...
    Mode.DICE -&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;즉, 이러한 enum Class + when 구조는 &lt;b&gt;모든 경우를 다 다루고 있다&lt;/b&gt;는 검증을 컴파일러가 체크해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;풀어 설명하면 enum class나 sealed class를 when문에 넣으면, 모든 가능한 값(분기)을 다 처리했는지 컴파일러가 검사한다는 뜻이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-end=&quot;139&quot; data-start=&quot;104&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;모든 경우를 빠짐없이 처리했다는 '보증'&lt;/b&gt;&lt;/h2&gt;
&lt;p data-end=&quot;152&quot; data-start=&quot;140&quot; data-ke-size=&quot;size16&quot;&gt;else는 &quot;나머지 값들은 다 여기로 들어와&quot; 라는 뜻이다.&lt;/p&gt;
&lt;p data-end=&quot;152&quot; data-start=&quot;140&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;152&quot; data-start=&quot;140&quot; data-ke-size=&quot;size16&quot;&gt;그런데 enum이나 sealed class는 &lt;b&gt;가능한 값이 명확히 한정돼 있기 때문에&lt;/b&gt;,&lt;br /&gt;else로 처리하면 오히려 컴파일러가&lt;b&gt; &amp;lsquo;빠진 케이스&amp;rsquo;를 인식할 수 없게 된다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;152&quot; data-start=&quot;140&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;305&quot; data-start=&quot;297&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어,&lt;/p&gt;
&lt;p data-end=&quot;305&quot; data-start=&quot;297&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1761907365363&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;enum class Mode { PICK, TEAM, RANK }

val pickNumbers = when (mode) {
    Mode.PICK -&amp;gt; ...
    Mode.TEAM -&amp;gt; ...
    else -&amp;gt; ...  // 여기서 RANK 포함됨
}&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;p data-end=&quot;588&quot; data-start=&quot;469&quot; data-ke-size=&quot;size16&quot;&gt;이 상태에서 나중에 Mode.DICE를 새로 추가해도,&lt;br /&gt;컴파일러는 &amp;ldquo;else 있으니까 괜찮네&amp;rdquo;라고 &lt;b&gt;경고를 하지 않는다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;588&quot; data-start=&quot;469&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;즉, DICE가 처리되지 않아도 앱은 그대로 빌드되어 버그 가능성이 높아진다.&lt;/p&gt;
&lt;p data-end=&quot;588&quot; data-start=&quot;469&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-end=&quot;615&quot; data-start=&quot;595&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;반면, else를 빼면?&lt;/b&gt;&lt;/h2&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1761907414259&quot; class=&quot;crystal&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;enum class Mode { PICK, TEAM, RANK }

val pickNumbers = when (mode) {
    Mode.PICK -&amp;gt; ...
    Mode.TEAM -&amp;gt; ...
    else -&amp;gt; ...  // 여기서 RANK 포함됨
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;760&quot; data-start=&quot;729&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;760&quot; data-start=&quot;729&quot; data-ke-size=&quot;size16&quot;&gt;이 상태에서 Mode.DICE를 enum에 추가하면?&lt;/p&gt;
&lt;p data-end=&quot;760&quot; data-start=&quot;729&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;760&quot; data-start=&quot;729&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&quot;when&amp;rsquo; expression must be exhaustive&quot;&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;760&quot; data-start=&quot;729&quot; data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 컴파일러가 강제로 알려준다.&lt;/p&gt;
&lt;p data-end=&quot;760&quot; data-start=&quot;729&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;926&quot; data-start=&quot;829&quot; data-ke-size=&quot;size16&quot;&gt;그래서 &lt;b&gt;모드가 추가되면 무조건 처리 코드를 써야 한다는 게 보장&lt;/b&gt;된다.&lt;br /&gt;즉, 미래의 나(혹은 동료)가 새로운 모드를 추가할 때 자동으로 &quot;리마인드&quot;가 되는 구조이다.&lt;/p&gt;
&lt;p data-end=&quot;588&quot; data-start=&quot;469&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;else를 대신 &lt;b&gt;exhaustive when&lt;/b&gt;을 사용하는 것은 단순한 취향 문제가 아니라,&lt;br /&gt;명확성 + 안정성 + 유지보수성을 크게 높여주는 것이다.&lt;/span&gt;&lt;/blockquote&gt;</description>
      <category>[앱 개발]/Kotlin</category>
      <author>yeonkims</author>
      <guid isPermaLink="true">https://btfnswt.tistory.com/214</guid>
      <comments>https://btfnswt.tistory.com/214#entry214comment</comments>
      <pubDate>Fri, 31 Oct 2025 19:51:50 +0900</pubDate>
    </item>
    <item>
      <title>콜백 함수를 이용한 관심사 분리</title>
      <link>https://btfnswt.tistory.com/213</link>
      <description>&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&amp;nbsp;removeTouchCallback의 역할은?&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;removeTouchCallback은 게임 로직에서 ViewModel로 &quot;이 터치를 화면에서 제거해줘&quot;라고 요청하는 콜백 함수이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp; &lt;/span&gt;역할:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp; &lt;/span&gt;- GameLogic &amp;rarr; ViewModel 통신: Strategy 패턴에서 게임 로직이 UI 상태를 직접 조작할 수 없으므로, 콜백을 통해 ViewModel에게 터치 제거를 요청&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp; &lt;/span&gt;- 관심사 분리: 게임 로직은 &quot;누구를 선택할지&quot; 결정만 하고, 실제 터치 데이터와 애니메이션 관리는 ViewModel이 담당&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp; &lt;/span&gt;- 의존성 역전: 게임 로직이 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;&lt;span&gt;&amp;nbsp; &lt;/span&gt;동작 흐름:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp; &lt;/span&gt;1. PickLogic이 랜덤 선택 후 removeTouchCallback(pointer)로 제거 요청&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp; &lt;/span&gt;2. ViewModel의 _removeTouch(pointer)가 실행되어 실제 터치 데이터 삭제와 애니메이션 해제&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;이렇게&lt;span&gt; &lt;/span&gt;하면&lt;span&gt; &lt;/span&gt;각&lt;span&gt; &lt;/span&gt;계층의&lt;span&gt; &lt;/span&gt;책임을&lt;span&gt;&amp;nbsp;&lt;/span&gt;명확히&lt;span&gt; &lt;/span&gt;분리할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;콜백 실행 순서&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;1.&amp;nbsp;finger_pick_view_model.dart:play&amp;nbsp;-&amp;nbsp;게임&amp;nbsp;시작,&amp;nbsp;_removeTouch&amp;nbsp;콜백을&amp;nbsp;GameEngine에&amp;nbsp;전달&lt;br /&gt;&amp;nbsp;&amp;nbsp;2.&amp;nbsp;game_engine.dart:run&amp;nbsp;-&amp;nbsp;받은&amp;nbsp;콜백을&amp;nbsp;GameLogic에&amp;nbsp;그대로&amp;nbsp;전달&lt;br /&gt;&amp;nbsp;&amp;nbsp;3.&amp;nbsp;game_logic.dart:PickLogic.run&amp;nbsp;-&amp;nbsp;랜덤&amp;nbsp;선택&amp;nbsp;후,&amp;nbsp;선택되지&amp;nbsp;않은&amp;nbsp;터치들에&amp;nbsp;대해&amp;nbsp;removeTouchCallback(pointer)&amp;nbsp;호출&lt;br /&gt;&amp;nbsp;&amp;nbsp;4.&amp;nbsp;finger_pick_view_model.dart:_removeTouch&amp;nbsp;-&amp;nbsp;콜백으로&amp;nbsp;실행되어&amp;nbsp;실제&amp;nbsp;터치&amp;nbsp;데이터&amp;nbsp;삭제&amp;nbsp;및&amp;nbsp;애니메이션&amp;nbsp;해제&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;흐름&amp;nbsp;요약:&lt;br /&gt;&amp;nbsp;&amp;nbsp;ViewModel.play&amp;nbsp;&amp;rarr;&amp;nbsp;GameEngine.run&amp;nbsp;&amp;rarr;&amp;nbsp;PickLogic.run&amp;nbsp;&amp;rarr;&amp;nbsp;ViewModel._removeTouch&lt;br /&gt;&lt;br /&gt;콜백은 PickLogic이 &quot;이 pointer는 제거해줘&quot;라고 ViewModel에게 간접적으로 요청하는 방식이다. 직접 참조 대신 콜백을 사용해서 Strategy 패턴의 의존성 분리를 유지할 수 있다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>[앱 개발]/Flutter, Dart</category>
      <author>yeonkims</author>
      <guid isPermaLink="true">https://btfnswt.tistory.com/213</guid>
      <comments>https://btfnswt.tistory.com/213#entry213comment</comments>
      <pubDate>Wed, 3 Sep 2025 19:12:42 +0900</pubDate>
    </item>
    <item>
      <title>Dart, Flutter의 required 키워드</title>
      <link>https://btfnswt.tistory.com/212</link>
      <description>&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-start=&quot;1310&quot; data-end=&quot;1333&quot; data-ke-size=&quot;size26&quot;&gt;required?&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot; data-start=&quot;1334&quot; data-end=&quot;1457&quot;&gt;
&lt;li data-start=&quot;1334&quot; data-end=&quot;1418&quot;&gt;Dart에서는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Named parameter(이름 있는 파라미터)&lt;/b&gt;가 많다.&lt;br /&gt;기본적으로는 optional(선택)이라서 안 넣어도 됨.&lt;/li&gt;
&lt;li data-start=&quot;1419&quot; data-end=&quot;1457&quot;&gt;required를 붙이면 &amp;ldquo;이거 꼭 줘야 돼&amp;rdquo;라는 의미가 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-start=&quot;1459&quot; data-end=&quot;1464&quot; data-ke-size=&quot;size16&quot;&gt;예시:&lt;/p&gt;
&lt;div style=&quot;color: #333333; text-align: start;&quot;&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1756204759142&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;void greet({required String name, int? age}) {
  print(&quot;Hello $name&quot;);
}

greet(name: &quot;Jiyeon&quot;); // OK
greet(); // ❌ 컴파일 에러 (name 안 줌)&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;rarr;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;즉, required는 &amp;ldquo;이 파라미터는 무조건 넣어야 한다&amp;rdquo;는 약속.&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-start=&quot;1613&quot; data-end=&quot;1760&quot; data-ke-size=&quot;size16&quot;&gt;(코틀린의 fun greet(name: String, age: Int? = null)과 비슷한데, Dart는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;named param&lt;/b&gt;에서 required로 강제할 수 있음.)&lt;/p&gt;</description>
      <category>[앱 개발]/Flutter, Dart</category>
      <author>yeonkims</author>
      <guid isPermaLink="true">https://btfnswt.tistory.com/212</guid>
      <comments>https://btfnswt.tistory.com/212#entry212comment</comments>
      <pubDate>Tue, 26 Aug 2025 19:39:28 +0900</pubDate>
    </item>
    <item>
      <title>Dart에서 final 키워드, 왜 이렇게 많이 쓰는가</title>
      <link>https://btfnswt.tistory.com/211</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;자바나 코틀린에서의 final = &amp;ldquo;상수(변경 불가)&amp;rdquo; 개념이랑 비슷함.&lt;br /&gt;근데 Dart는 이걸 &lt;b&gt;아주 광범위하게 적용&lt;/b&gt;할 수 있음:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1004&quot; data-start=&quot;520&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;638&quot; data-start=&quot;520&quot;&gt;&lt;b&gt;변수/필드 앞 &lt;/b&gt;&amp;rarr; 값 재할당 불가 (Java의 final 변수, Kotlin의 val)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1756204521926&quot; class=&quot;actionscript&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;final name = &quot;Jiyeon&quot;; // 한 번만 할당 가능&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1004&quot; data-start=&quot;520&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;760&quot; data-start=&quot;640&quot;&gt;&lt;b&gt;클래스 앞 &lt;/b&gt;&amp;rarr; 이 클래스는 더 이상 상속할 수 없음 (Java의 final class, Kotlin의 final 기본값과 비슷)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1756204541425&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;final class A {}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1004&quot; data-start=&quot;520&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;915&quot; data-start=&quot;762&quot;&gt;&lt;b&gt;생성자 매개변수 앞 &lt;/b&gt;&amp;rarr; 읽기 전용 필드로 고정됨
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1756204570636&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Person {
  final String name;
  Person(this.name); // 여기서 name은 생성자 때만 정해지고 이후 변경 불가
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1004&quot; data-start=&quot;520&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1004&quot; data-start=&quot;917&quot;&gt;&lt;b&gt;지역 변수, 루프 변수 등등&lt;/b&gt;&lt;br /&gt;&amp;rarr; 어디서든 값 재할당 막을 때 final을 써서 &lt;b&gt;불변성(immutability)&lt;/b&gt;을 강조하는 것.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-end=&quot;1028&quot; data-start=&quot;1006&quot; data-ke-size=&quot;size23&quot;&gt;왜 이렇게 final을 강조할까?&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1197&quot; data-start=&quot;1029&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1058&quot; data-start=&quot;1029&quot;&gt;&lt;b&gt;버그 예방&lt;/b&gt;: 실수로 값 바꾸는 걸 막음&lt;/li&gt;
&lt;li data-end=&quot;1096&quot; data-start=&quot;1059&quot;&gt;&lt;b&gt;가독성&lt;/b&gt;: &amp;ldquo;이 값은 절대 안 바뀌는구나&amp;rdquo; 한눈에 보임&lt;/li&gt;
&lt;li data-end=&quot;1146&quot; data-start=&quot;1097&quot;&gt;&lt;b&gt;최적화&lt;/b&gt;: Dart VM/컴파일러가 불변값을 더 공격적으로 최적화할 수 있음&lt;/li&gt;
&lt;li data-end=&quot;1197&quot; data-start=&quot;1147&quot;&gt;&lt;b&gt;의도 표현&lt;/b&gt;: 협업 시 다른 개발자에게 &amp;ldquo;여긴 불변이야!&amp;rdquo;라고 명확하게 알려줌&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1303&quot; data-start=&quot;1199&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;&amp;rarr;&lt;span&gt; 즉,&lt;/span&gt;&lt;/span&gt; Dart/Flutter 커뮤니티에서는 &lt;b&gt;&amp;ldquo;가능하면 무조건 final 써라&amp;rdquo;&lt;/b&gt;가 일종의 베스트 프랙티스다.&lt;br /&gt;코틀린에서 val을 기본으로 쓰는 거랑 같은 철학이라고 보면 됨.&lt;/p&gt;</description>
      <category>[앱 개발]/Flutter, Dart</category>
      <author>yeonkims</author>
      <guid isPermaLink="true">https://btfnswt.tistory.com/211</guid>
      <comments>https://btfnswt.tistory.com/211#entry211comment</comments>
      <pubDate>Tue, 26 Aug 2025 19:39:02 +0900</pubDate>
    </item>
    <item>
      <title>Strategy 패턴 실제 적용 정리</title>
      <link>https://btfnswt.tistory.com/209</link>
      <description>&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;game_engine.dart에 모두 때려박아야 하는가?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(예를들어 GameEngine 클래스의 gameResult()라는 메소드에서 게임 모드에 따라 switch문으로 분기시키기)&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;아니면 모드를 전략(Strategy)으로 분리하는게 좋은가?&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;당연히 Strategy 패턴을 이용하여 테스트&amp;middot;확장성&amp;middot;가독성&amp;middot;버그 격리도를 향상시키는게 좋다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-start=&quot;3178&quot; data-end=&quot;3225&quot;&gt;&lt;b&gt;확장성&lt;/b&gt;: 새로운 모드 추가해도 기존 코드 최소 변경(레지스트리에만 등록).&lt;/li&gt;
&lt;li data-start=&quot;3226&quot; data-end=&quot;3277&quot;&gt;&lt;b&gt;테스트 용이&lt;/b&gt;: 각 로직을&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;단독 유닛테스트&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;가능, seed로 결과 재현.&lt;/li&gt;
&lt;li data-start=&quot;3278&quot; data-end=&quot;3339&quot;&gt;&lt;b&gt;UI 분리&lt;/b&gt;: 엔진은 순수 로직, ChangeNotifier/ViewModel은 상태만 관리.&lt;/li&gt;
&lt;li data-start=&quot;3340&quot; data-end=&quot;3389&quot;&gt;&lt;b&gt;안전성&lt;/b&gt;: 참가자 수/팀 개수 검증을 각 전략에 국한 &amp;rarr; 버그 영향 범위 축소.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-end=&quot;3853&quot; data-start=&quot;3826&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&quot;switch 한 방&quot; 접근이 안 좋은 이유&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;3972&quot; data-start=&quot;3854&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;3889&quot; data-start=&quot;3854&quot;&gt;모드가 늘수록 switch가 비대해지고 테스트가 어려워짐&lt;/li&gt;
&lt;li data-end=&quot;3917&quot; data-start=&quot;3890&quot;&gt;공통/특수 케이스 섞이면서 사이드이펙트 늘어남&lt;/li&gt;
&lt;li data-end=&quot;3972&quot; data-start=&quot;3918&quot;&gt;OCP(Open-Closed Principle) 위반: 새 모드 추가 때 기존 코드 자주 수정&lt;/li&gt;
&lt;/ul&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;b&gt;순수(Pure) 로직&lt;/b&gt;으로 두고, UI/상태는 ViewModel이 맡게 하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;188&quot; data-start=&quot;159&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;설계 구조 (Strategy 패턴 + Registry)&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;290&quot; data-start=&quot;189&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;207&quot; data-start=&quot;189&quot;&gt;엔진은 공통 인터페이스만 노출&lt;/li&gt;
&lt;li data-end=&quot;247&quot; data-start=&quot;208&quot;&gt;각 모드별 로직은 별도 클래스로 분리&lt;/li&gt;
&lt;li data-end=&quot;290&quot; data-start=&quot;248&quot;&gt;enum &amp;harr; 전략 매핑은 레지스트리(Map)로 관리&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1756200955339&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;enum GameMode&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;bull;&lt;span&gt;&amp;nbsp;아래에&amp;nbsp;&lt;/span&gt;&lt;/span&gt;sealed class GameResult를 만들었는데 왜 여전히 enum GameMode는 필요한가?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;enum GameMode&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;205&quot; data-start=&quot;162&quot;&gt;&lt;b&gt;게임을 실행하기 전에 &amp;ldquo;어떤 모드로 돌릴지&amp;rdquo;를 선택&lt;/b&gt;하는 정보.&lt;/li&gt;
&lt;li data-end=&quot;234&quot; data-start=&quot;206&quot;&gt;즉, &lt;b&gt;입력(선택지)&lt;/b&gt; 단계에서 필요.&lt;/li&gt;
&lt;li data-end=&quot;302&quot; data-start=&quot;235&quot;&gt;ViewModel이나 UI에서 사용자가 &quot;A / B / C&quot;를 고르면 이게 바로 GameMode.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;sealed class GameResult&lt;/b&gt;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;501&quot; data-start=&quot;458&quot;&gt;반대로, &lt;b&gt;게임을 실제로 실행한 뒤 나오는 결과의 형태&lt;/b&gt;를 나타냄.&lt;/li&gt;
&lt;li data-end=&quot;529&quot; data-start=&quot;502&quot;&gt;즉, &lt;b&gt;출력(결과)&lt;/b&gt; 단계에서 필요.&lt;/li&gt;
&lt;li data-end=&quot;580&quot; data-start=&quot;530&quot;&gt;A모드면 AResult, B모드면 BResult 이런 식으로 갈라짐.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(비유)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;enum GameMode =&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;게임 메뉴판&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;(어떤 게임을 고를지 선택)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;sealed GameResult =&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;게임 끝나고 나온 점수판/결과표&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-start=&quot;854&quot; data-end=&quot;885&quot; data-ke-size=&quot;size16&quot;&gt;둘은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;시점과 책임이 다르기 때문에 둘 다 필요.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-start=&quot;854&quot; data-end=&quot;885&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;892&quot; data-end=&quot;914&quot;&gt;만약 GameMode를 없애면?&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot; data-start=&quot;915&quot; data-end=&quot;1032&quot;&gt;
&lt;li data-start=&quot;915&quot; data-end=&quot;956&quot;&gt;UI에서 &quot;Pick 모드로 실행&quot; 같은 선택을 표현하기가 힘들어짐.&lt;/li&gt;
&lt;li data-start=&quot;957&quot; data-end=&quot;1032&quot;&gt;그럼 결국 PickLogic, TeamLogic, RankLogic을 직접 객체로 다뤄야 함 &amp;rarr; 코드가 더 복잡해짐.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-start=&quot;1034&quot; data-end=&quot;1095&quot; data-ke-size=&quot;size16&quot;&gt;즉, GameMode는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;UI/엔진 사이의 단순한 계약(토글 값)&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;역할을 해서 실무에서 굉장히 편함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1756201019172&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 1) 결과 타입 : sealed class로 모드별 결과 표현 
sealed class GameResult {}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;bull; 비어있는 GameResult로 굳이 게임별 결과를 표현하는 이유&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; &lt;b&gt;모든 게임 결과의 부모 틀&lt;/b&gt;이라고 보면 됨.&lt;br /&gt;각 게임의 결과가 전부 GameResult를 상속하니까, 최종 엔진에서는 그냥 &lt;b&gt;GameResult 하나만 반환&lt;/b&gt;하면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1756201025929&quot; class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;// 1-2) 모드별 결과 정의 
final class ~Result extends GameResult { 
	~Result(this.x) 
}&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_1756201084254&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 2) 전략 인터페이스
abstract interface class GameLogic { 
	GameResult run({&amp;hellip;}) 
}&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;bull; 전략 인터페이스?&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;rarr;&lt;span&gt; 전략 패턴(Strategy Pattern)&amp;rdquo;이란 디자인 패턴 사용&lt;br /&gt;&lt;b&gt;알고리즘(전략)을 외부로 빼서 갈아 끼우는 구조&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&lt;b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;581&quot; data-start=&quot;542&quot;&gt;GameLogic = 게임 돌리는 방법을 약속한 틀(계약).&lt;/li&gt;
&lt;li data-end=&quot;638&quot; data-start=&quot;582&quot;&gt;ALogic, BLogic, CLogic = 실제 게임별 전략(알고리즘).&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;abstract interface class라는 긴 키워드는 Dart3에서 새로 생긴 문법.&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;Dart에서 저렇게 쓴 건, 자바/코틀린으로 치면 그냥 interface GameLogic이랑 제일 가까움.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(&lt;b&gt;Java/Kotlin&lt;/b&gt;: abstract interface 같은 건 없음. interface와 abstract class가 별도로 존재.)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;843&quot; data-start=&quot;726&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;147&quot; data-start=&quot;93&quot;&gt;&lt;b&gt;abstract&lt;/b&gt; = 이 클래스는 직접 new 못 함 (미구현 메소드 있어도 됨)&lt;/li&gt;
&lt;li data-end=&quot;275&quot; data-start=&quot;148&quot;&gt;&lt;b&gt;interface class&lt;/b&gt; = 다른 클래스가 &lt;b&gt;상속(extends)&lt;/b&gt; 은 못 하고, 오직 &lt;b&gt;구현(implements)&lt;/b&gt; 만 가능&lt;br /&gt;&amp;rarr; 즉, &quot;진짜 인터페이스처럼만 쓰이는 추상 클래스&quot;를 만든 것.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1756201111855&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 3) 공통 설정 (팀 개수 등) 
class GameConfig&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_1756201129471&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 4) 모드별 구현 
final class ~Logic implements GameLogic&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;final class?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;이 클래스를 &lt;b&gt;더 이상 상속 못 한다&lt;/b&gt;는 의미.&lt;br /&gt;즉, ~Logic은 그냥 딱 하나의 확정된 구현체! 라는 뜻.&lt;/p&gt;
&lt;p data-end=&quot;1444&quot; data-start=&quot;1345&quot; data-ke-size=&quot;size16&quot;&gt;implements GameLogic &amp;rarr; GameLogic(계약서)에 적힌 함수들을 반드시 구현해야 한다.&lt;br /&gt;즉, &amp;ldquo;이 클래스는 Pick 전략을 담당한다&amp;rdquo;라고 선언하는 것임.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1756201140072&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 5) 엔진 (순수함수 스타일, 상태 없음) 
final class GameEngine&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1496&quot; data-start=&quot;1451&quot; data-ke-size=&quot;size16&quot;&gt;&amp;bull; GameEngine을 왜 상태 없이 만들었는가?&lt;/p&gt;
&lt;p data-end=&quot;1526&quot; data-start=&quot;1497&quot; data-ke-size=&quot;size16&quot;&gt;&amp;rarr;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt; &lt;b&gt;순수 계산기&lt;/b&gt;처럼 만들고 싶어서.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1612&quot; data-start=&quot;1527&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1549&quot; data-start=&quot;1527&quot;&gt;입력 넣으면 &amp;rarr; 결과 뱉음. 끝.&lt;/li&gt;
&lt;li data-end=&quot;1586&quot; data-start=&quot;1550&quot;&gt;안에 상태가 없으니까, 여러 번 호출해도 항상 예측 가능.&lt;/li&gt;
&lt;li data-end=&quot;1612&quot; data-start=&quot;1587&quot;&gt;테스트하기 쉽고, UI/상태랑 분리됨.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1662&quot; data-start=&quot;1614&quot; data-ke-size=&quot;size16&quot;&gt;즉, &lt;b&gt;&amp;ldquo;엔진은 계산만 한다, 나머지는 ViewModel이 관리한다&amp;rdquo;&lt;/b&gt; 이런 철학.&lt;/p&gt;
&lt;p data-end=&quot;1662&quot; data-start=&quot;1614&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1662&quot; data-start=&quot;1614&quot; data-ke-size=&quot;size16&quot;&gt;&amp;bull;&amp;nbsp;엔진부분의 registry?&lt;/p&gt;
&lt;p data-end=&quot;1662&quot; data-start=&quot;1614&quot; data-ke-size=&quot;size16&quot;&gt;&amp;rarr;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;registry는 쉽게 말해서 &lt;b&gt;모드 &amp;harr; 전략 객체&amp;rdquo;를 연결해둔 Map &lt;/b&gt;이다.&lt;/p&gt;
&lt;pre id=&quot;code_1756203090474&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;final Map&amp;lt;GameMode, GameLogic&amp;gt; _registry = {
  GameMode.A: ALogic(),
  GameMode.B: BLogic(),
  GameMode.C: CLogic(),
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GameEngine이 직접 switch를 쓰지 않고, 이 레지스트리에서 찾아서 실행하는 것.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, &lt;b&gt;중앙에서 모드별 구현체를 등록/관리&lt;/b&gt;하는 구조.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2238&quot; data-start=&quot;2196&quot;&gt;Strategy 패턴 = 알고리즘을 인터페이스로 빼고 갈아 끼우는 것&lt;/li&gt;
&lt;li data-end=&quot;2286&quot; data-start=&quot;2239&quot;&gt;Registry = 미리 Map 같은 데에 등록해두고 필요할 때 꺼내 쓰는 것&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 목적은: &lt;b&gt;switch 문 안 쓰고 깔끔하게 확장성 확보&lt;/b&gt;.&lt;br /&gt;새 모드 추가하면 그냥 registry에만 등록하면 끝.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>[앱 개발]/Flutter, Dart</category>
      <author>yeonkims</author>
      <guid isPermaLink="true">https://btfnswt.tistory.com/209</guid>
      <comments>https://btfnswt.tistory.com/209#entry209comment</comments>
      <pubDate>Tue, 26 Aug 2025 19:14:12 +0900</pubDate>
    </item>
    <item>
      <title>Dart - 자동 기본 생성자</title>
      <link>https://btfnswt.tistory.com/208</link>
      <description>&lt;pre id=&quot;code_1756200363096&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class A { A(); }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-end=&quot;66&quot; data-start=&quot;22&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;66&quot; data-start=&quot;22&quot; data-ke-size=&quot;size16&quot;&gt;이 코드는 &lt;b&gt;Dart 문법&lt;/b&gt;으로 작성된 클래스 선언이다.&lt;/p&gt;
&lt;p data-end=&quot;66&quot; data-start=&quot;22&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;66&quot; data-start=&quot;22&quot; data-ke-size=&quot;size16&quot;&gt;의미를 풀어보면:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-end=&quot;258&quot; data-start=&quot;68&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;121&quot; data-start=&quot;68&quot;&gt;&lt;b&gt;class A { ... }&lt;/b&gt;&lt;br /&gt;&amp;rarr; A라는 이름의 클래스를 정의한 것.&lt;/li&gt;
&lt;li data-end=&quot;258&quot; data-start=&quot;123&quot;&gt;&lt;b&gt;A();&lt;/b&gt;&lt;br /&gt;&amp;rarr; A 클래스의 기본 생성자(default constructor) 를 명시적으로 정의한 것.&lt;br /&gt;Dart에서는 생성자를 생략하면 자동으로 A(); 라는 &lt;b&gt;기본 생성자&lt;/b&gt;가 제공됨.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-end=&quot;285&quot; data-start=&quot;260&quot; data-ke-size=&quot;size16&quot;&gt;즉, 아래 두 코드는 완전히 동일한 의미이다:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1756200407939&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class A { A(); // 기본 생성자 (직접 정의) }


class A { // 아무 생성자도 정의하지 않으면, 자동으로 A(); 생성자가 만들어짐 }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;479&quot; data-start=&quot;405&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt; &lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;정리하자면:&lt;br /&gt;이 코드는 &lt;b&gt;&quot;A라는 클래스를 만들고, 매개변수가 없는 기본 생성자를 &lt;i&gt;명시적으로&lt;/i&gt; 선언한다&quot;&lt;/b&gt;는 뜻&lt;/p&gt;
&lt;p data-end=&quot;479&quot; data-start=&quot;405&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-end=&quot;941&quot; data-start=&quot;919&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;언어별 기본 생성자 여부&lt;/b&gt;&lt;/h4&gt;
&lt;div&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 93px;&quot; border=&quot;1&quot; data-end=&quot;1147&quot; data-start=&quot;943&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 19px;&quot; data-end=&quot;1036&quot; data-start=&quot;1005&quot;&gt;
&lt;td style=&quot;height: 19px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;1016&quot; data-start=&quot;1005&quot;&gt;&lt;b&gt;C&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;1036&quot; data-start=&quot;1016&quot;&gt;❌ (개념 없음)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot; data-end=&quot;1109&quot; data-start=&quot;1074&quot;&gt;
&lt;td style=&quot;height: 19px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;1087&quot; data-start=&quot;1074&quot;&gt;&lt;b&gt;Kotlin&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;1109&quot; data-start=&quot;1087&quot;&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot; data-end=&quot;1147&quot; data-start=&quot;1110&quot;&gt;
&lt;td style=&quot;height: 19px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;1123&quot; data-start=&quot;1110&quot;&gt;&lt;b&gt;Java&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;1147&quot; data-start=&quot;1123&quot;&gt;✅ (단, 다른 생성자 정의하면 ❌)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot; data-end=&quot;1073&quot; data-start=&quot;1037&quot;&gt;
&lt;td style=&quot;height: 19px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;1050&quot; data-start=&quot;1037&quot;&gt;&lt;b&gt;Python&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;1073&quot; data-start=&quot;1050&quot;&gt;✅ (object.__&lt;b&gt;init__&lt;/b&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;188&quot; data-start=&quot;175&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Flutter 코드에서 ChangeNotifier 기반 ViewModel 만들 때&lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;ClassName(); 식으로 생성자를 직접 정의하는 경우가 종종 나온다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;188&quot; data-start=&quot;175&quot; data-ke-size=&quot;size16&quot;&gt;왜 이렇게 쓰냐?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;명시적으로 써주는 이유&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;529&quot; data-start=&quot;379&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;407&quot; data-start=&quot;379&quot;&gt;나중에 생성자에 매개변수 넣을 가능성을 대비&lt;/li&gt;
&lt;li data-end=&quot;480&quot; data-start=&quot;411&quot;&gt;super 생성자 호출을 명시해야 하는 경우 (예: super(key) in StatelessWidget)&lt;/li&gt;
&lt;li data-end=&quot;529&quot; data-start=&quot;484&quot;&gt;코드를 읽는 사람 입장에서 &amp;ldquo;여기에 생성자가 있다&amp;rdquo;는 걸 눈에 보이게 해줌&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  즉, &lt;b&gt;없어도 되는데 &amp;ldquo;명시성&amp;rdquo; 때문에 작성&lt;/b&gt;하는 경우가 많다.&lt;/p&gt;</description>
      <category>[앱 개발]/Flutter, Dart</category>
      <author>yeonkims</author>
      <guid isPermaLink="true">https://btfnswt.tistory.com/208</guid>
      <comments>https://btfnswt.tistory.com/208#entry208comment</comments>
      <pubDate>Tue, 26 Aug 2025 18:29:32 +0900</pubDate>
    </item>
  </channel>
</rss>