๊นจ์•Œ ๊ฐœ๋… ๐Ÿ“‘/Android

[Android] LiveData - ์–‘๋ฐฉํ–ฅ ๋ฐ์ดํ„ฐ ๋ฐ”์ธ๋”ฉ์˜ 3๊ฐ€์ง€ ๋ฐฉ๋ฒ•

interfacer_han 2024. 1. 20. 15:35

#1 ๋‹จ๋ฐฉํ–ฅ ๋ฐ์ดํ„ฐ ๋ฐ”์ธ๋”ฉ

#1-1 ๋‹จ๋ฐฉํ–ฅ๊ณผ ์–‘๋ฐฉํ–ฅ

์ง€๊ธˆ๊นŒ์ง€ ํ•ด์˜จ Data Binding์€ ๋‹จ๋ฐฉํ–ฅ(One Way) ๋ฐ์ดํ„ฐ ๋ฐ”์ธ๋”ฉ์ด์—ˆ๋‹ค. Model ๋˜๋Š” ViewModel์—์„œ View๋กœ ๊ฐ€๋Š” ํ๋ฆ„์œผ๋กœ๋งŒ ๋ฐ์ดํ„ฐ๊ฐ€ ๊ฐฑ์‹ ๋œ๋‹ค. ์—ญ์€ ์„ฑ๋ฆฝํ•˜์ง€ ์•Š๋Š”๋‹ค. ์—ญ์ด ์„ฑ๋ฆฝํ•˜์ง€ ์•Š๋Š” ๊ฒŒ ๊ผญ ๋‚˜์œ ๊ฒƒ๋„ ์•„๋‹ˆ๋‹ค. ๋งˆ์น˜ ์ธํ„ฐ๋„ท ์‡ผํ•‘๋ชฐ์—์„œ ์–ด๋–ค ๋ฌผ๊ฑด์˜ ํ‘œ์‹œ๋œ ๊ฐ€๊ฒฉ์„, ๋ธŒ๋ผ์šฐ์ €์˜ ๊ฐœ๋ฐœ์ž ๋ชจ๋“œ๋ฅผ ํ†ตํ•ด ๋ณ€๊ฒฝํ•œ๋‹ค๊ณ  ์„œ๋ฒ„์— ์ €์žฅ๋œ ์‹ค์ œ ๊ฐ€๊ฒฉ์ด ๋ณ€ํ•˜์ง€ ์•Š๋Š” ๊ฒƒ๊ณผ ๊ฐ™๋‹ค. ๋งŒ์— ํ•˜๋‚˜ ๋ณ€ํ•˜๋Š” ์ผ๋„ ์—†์–ด์•ผํ•˜๊ณ  ๋ง์ด๋‹ค.
 

#1-2 ์ˆ˜์ •ํ•  ์ƒ˜ํ”Œ ์•ฑ

[Android] LiveData - ์•”์‹œ์ ์œผ๋กœ '๊ด€์ฐฐ'ํ•˜๊ธฐ

#1 ViewModel ์†์— LiveData๊ฐ€ ์žˆ๋Š” ์ƒ˜ํ”Œ ์•ฑ [Android] ViewModel - View์— ๊ฐ์ฒด(ViewModel) ์ „๋‹ฌ #1 ๊ฐœ์š” #1-1 Data Binding๊ณผ ViewModel [Android] Data Binding - View์— ๊ฐ์ฒด ์ „๋‹ฌ #1 ๊ฐ์ฒด ์ „๋‹ฌ์˜ ํ•„์š”์„ฑ #1-1 ์ด์ „ ๊ธ€ Data Binding -

kenel.tistory.com

์œ„ ๊ฒŒ์‹œ๊ธ€์˜ ์™„์„ฑ๋œ ์•ฑ์„ ์ˆ˜์ •ํ•ด์„œ, ๋‹จ๋ฐฉํ–ฅ ๋ฐ์ดํ„ฐ ๋ฐ”์ธ๋”ฉ์˜ ์ž‘๋™์„ ํ™•์ธํ•ด๋ณธ๋‹ค.
 

#1-3 activity_main.xml ์ˆ˜์ •

<?xml version="1.0" encoding="utf-8"?>

<layout ...>

    ...

    <androidx.constraintlayout.widget.ConstraintLayout ...>

        ...

        <EditText ... />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

<TextView>๋ฅผ <EditText>๋กœ ๋ฐ”๊พผ๋‹ค
 

#1-4 ์ž‘๋™ ํ™•์ธ - ๋‹จ๋ฐฉํ–ฅ ๋ฐ์ดํ„ฐ ๋ฐ”์ธ๋”ฉ

<Button>์„ 7๋ฒˆ ํด๋ฆญํ•œ๋‹ค. ์ด๋Ÿฌ๋ฉด, ViewModel์˜ ํ”„๋กœํผํ‹ฐ count์— ๋Œ€ํ•ด, MainActivityViewModel.updateCount(){ count++ }๊ฐ€ 7๋ฒˆ ์ˆ˜ํ–‰๋œ๋‹ค. ๋‹จ๋ฐฉํ–ฅ ๋ฐ์ดํ„ฐ ๋ฐ”์ธ๋”ฉ์— ์˜ํ•ด <EditText>์—๋Š” 7์ด ๋‹ด๊ธด๋‹ค.
 
์ด๋ฒˆ์—” ์‚ฌ์šฉ์ž(๋‚˜)๊ฐ€ ์ง์ ‘ <EditText>๋ฅผ ์กฐ์ž‘ํ•œ๋‹ค. 7์„ 3์œผ๋กœ ๋ฐ”๊พผ๋‹ค. ์ด ๋•Œ, ๋‚ด๋ถ€์ ์ธ count์˜ ๊ฐ’์€ ์—ฌ์ „ํžˆ 7์ด๋‹ค. ์‚ฌ์šฉ์ž๊ฐ€ ์ง์ ‘ <EditText>๋ฅผ ์กฐ์ž‘ํ•œ๋‹ค๊ณ , ViewModel์˜ ํ”„๋กœํผํ‹ฐ์— ์–ด๋–ค ์˜ํ–ฅ์„ ๋ฏธ์น  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ๊ทธ๋ž˜์„œ <Button>์„ ํด๋ฆญํ•˜๋ฉด <EditText>์—๋Š” 4๊ฐ€ ์•„๋‹Œ 7+1 = 8์ด ๋‹ด๊ธด๋‹ค. 
 

#2 ์–‘๋ฐฉํ–ฅ ๋ฐ์ดํ„ฐ ๋ฐ”์ธ๋”ฉ

#2-1 ์–‘๋ฐฉํ–ฅ ๋ฐ์ดํ„ฐ ๋ฐ”์ธ๋”ฉ์ด '์•”์‹œ'ํ•˜๋Š” ๊ฒƒ

์ด์ฏค๋˜๋ฉด ์–‘๋ฐฉํ–ฅ(Two Way) ๋ฐ์ดํ„ฐ ๋ฐ”์ธ๋”ฉ์— ๋Œ€ํ•œ ๊ทธ๋ฆผ์ด ๋ณด์ธ๋‹ค. ๋ฐ”๋กœ ๊ธฐ์กด์˜ ๋‹จ๋ฐฉํ–ฅ ๋ฐ์ดํ„ฐ ๋ฐ”์ธ๋”ฉ์— ์ถ”๊ฐ€ํ•ด, View๋‹จ์—์„œ ์ด๋ค„์ง€๋Š” ์‚ฌ์šฉ์ž์˜ ์ž…๋ ฅ์„ ๋‚ด๋ถ€ ํ”„๋กœํผํ‹ฐ์— ์•”์‹œ์ ์œผ๋กœ ๋Œ€์ž…ํ•˜๋Š” ๊ฒƒ์ด๋‹ค. ๋งŒ์•ฝ, ์–‘๋ฐฉํ–ฅ ๋ฐ์ดํ„ฐ ๋ฐ”์ธ๋”ฉ์˜ ์ฝ”๋“œ๋ฅผ ํ”„๋กœ๊ทธ๋ž˜๋จธ๊ฐ€ ๋ช…์‹œ์ ์œผ๋กœ ๋ฐํ˜€ ์ ๋Š”๋‹ค๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํ˜•ํƒœ์ผ ๊ฒƒ์ด๋‹ค.
 

...

class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding
    private lateinit var viewModel: MainActivityViewModel
    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        
        binding.countText.addTextChangedListener(object : TextWatcher {
            override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
                // <EditText>์— ๋‹ด๊ธด ๊ฐ’์— ๋ณ€ํ™”๊ฐ€ ์ƒ๊ธธ ๋•Œ ์ˆ˜ํ–‰ํ•  ์ผ
            }

            override fun afterTextChanged(s: Editable?) {
                // <EditText>์— ๋Œ€ํ•œ ์‚ฌ์šฉ์ž์˜ ์ž…๋ ฅ์ด ๋๋‚ฌ์„ ๋•Œ ์ˆ˜ํ–‰ํ•  ์ผ
            }

            override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
                // <EditText>์— ๋Œ€ํ•œ ์‚ฌ์šฉ์ž์˜ ์ž…๋ ฅ์ด ๋“ค์–ด์˜ค๊ธฐ ์ง์ „์— ํ•  ์ผ
            }
        })
    }
}

์ด๋Ÿฌํ•œ ์ฝ”๋“œ๋ฅผ ์•”์‹œ์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•˜๋Š” ๊ฒŒ ๋ฐ”๋กœ ์–‘๋ฐฉํ–ฅ ๋ฐ์ดํ„ฐ ๋ฐ”์ธ๋”ฉ์ด๋‹ค.
 

#2-2 ์–‘๋ฐฉํ–ฅ ๋ฐ์ดํ„ฐ ๋ฐ”์ธ๋”ฉ์„ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด ์ฐธ์กฐํ•œ ์›น์‚ฌ์ดํŠธ

์–‘๋ฐฉํ–ฅ ๋ฐ์ดํ„ฐ ๊ฒฐํ•ฉ  |  Android ๊ฐœ๋ฐœ์ž  |  Android Developers

์–‘๋ฐฉํ–ฅ ๋ฐ์ดํ„ฐ ๊ฒฐํ•ฉ ์ปฌ๋ ‰์…˜์„ ์‚ฌ์šฉํ•ด ์ •๋ฆฌํ•˜๊ธฐ ๋‚ด ํ™˜๊ฒฝ์„ค์ •์„ ๊ธฐ์ค€์œผ๋กœ ์ฝ˜ํ…์ธ ๋ฅผ ์ €์žฅํ•˜๊ณ  ๋ถ„๋ฅ˜ํ•˜์„ธ์š”. ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋‹จ๋ฐฉํ–ฅ ๋ฐ์ดํ„ฐ ๊ฒฐํ•ฉ์„ ์‚ฌ์šฉํ•˜๋ฉด ์†์„ฑ์— ๊ฐ’์„ ์„ค์ •ํ•˜๊ณ  ์ด ์†์„ฑ์˜ ๋ณ€๊ฒฝ์— ๋ฐ˜

developer.android.com

#3 ~ #5์˜ ๋ชจ๋“  ์ฝ”๋“œ๋Š” ์œ„ ๋งํฌ ๊ทธ๋ฆฌ๊ณ  ๊ตฌ๊ธ€ ๊ณต์‹ ์ƒ˜ํ”Œ์„ ์ฐธ์กฐํ•˜์—ฌ ์งฐ๋‹ค.
 
์ด์ œ๋ถ€ํ„ฐ ์–‘๋ฐฉํ–ฅ ๋ฐ์ดํ„ฐ ๋ฐ”์ธ๋”ฉ์„ ์‚ฌ์šฉํ•ด๋ณธ๋‹ค. ๋ณธ ๊ฒŒ์‹œ๊ธ€์—์„  ์ด 3๊ฐ€์ง€ ๋ฐฉ๋ฒ•์œผ๋กœ ์–‘๋ฐฉํ–ฅ ๋ฐ์ดํ„ฐ ๋ฐ”์ธ๋”ฉ์„ ๊ตฌํ˜„ํ–ˆ๋‹ค.
 

#3 Converter ์ด์šฉ (๋ฐฉ๋ฒ• 1)

#3-1 Project ์ˆ˜์ค€์˜ build.gradle.kts์— KSP(์–ด๋…ธํ…Œ์ด์…˜ ๋ฌธ๋ฒ• ์‚ฌ์šฉ์„ ์œ„ํ•œ API) ํ”Œ๋Ÿฌ๊ทธ์ธ ์ถ”๊ฐ€

// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
    ...

    id("com.google.devtools.ksp") version "1.9.0-1.0.13" apply false 
}

KSP GitHub ํŽ˜์ด์ง€์—์„œ ํ”„๋กœ์ ํŠธ์˜ Kotlin ๋ฒ„์ „์— ๋งž๋Š” KSP ๋ฒ„์ „์„ ์„ ํƒํ•ด plugins { ... }์— ์ถ”๊ฐ€ํ•œ๋‹ค.
 

Kotlin์˜ ๋ฒ„์ „์€ ์•ˆ๋“œ๋กœ์ด๋“œ ์ŠคํŠœ๋””์˜ค ์ฐฝ์˜ [File] - [Settings] - [Languages & Frameworks] - [Kotlin]์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.
 

#3-2 Module ์ˆ˜์ค€์˜ build.gradle.kts์— KSP๋ฅผ ์‚ฌ์šฉํ•˜๊ฒŒ ์„ค์ •

plugins {
    ...
    
    id("com.google.devtools.ksp")
    id("org.jetbrains.kotlin.kapt")
}

android {
    ...
}

dependencies {
    ...
}

kapt๋Š” KSP์˜ ๊ตฌ๋ฒ„์ „์ด๋‹ค. ํ•˜์ง€๋งŒ, ๋ฐ์ดํ„ฐ ๋ฐ”์ธ๋”ฉ ์–ด๋…ธํ…Œ์ด์…˜ ์‚ฌ์šฉ์„ ์œ„ํ•ด์„œ๋Š” kapt๊ฐ€ ์—ฌ์ „ํžˆ ํ•„์š”ํ•˜๋‹ค๊ณ  ํ•œ๋‹ค. ๊ทธ๋ž˜์„œ kapt๋„ plugins { ... }์— ๋„ฃ๋Š”๋‹ค.
 

#3-3 MyConverter.kt ๋งŒ๋“ค๊ธฐ

// package com.example.twowaybindingusingconverter

import android.widget.EditText
import androidx.databinding.InverseMethod

object MyConverter {
    @InverseMethod("stringToInt")
    @JvmStatic
    fun intToString(
        view: EditText, value: Int
    ): String {
        // ์‚ฌ์šฉ์ž๊ฐ€ ์Œ์ˆ˜๋ฅผ ์ž…๋ ฅํ•  ๋•Œ, EditText.text๊ฐ€ "ํšŒ" or "-ํšŒ"์ด ๋˜๋Š” ์ˆœ๊ฐ„ "0ํšŒ"๋กœ ๊ฐฑ์‹ ๋˜์ง€ ์•Š๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•œ if๋ฌธ
        if (value == 0) {
            return view.text.toString()
        }

        return value.toString() + "ํšŒ"
    }

    @JvmStatic
    fun stringToInt(
        view: EditText, value: String
    ): Int {
        return value.replace("ํšŒ", "").toIntOrNull() ?: 0
    }
}

intToString()์€ ViewModel์—์„œ ๋ณด๋‚ธ ์–ด๋–ค ๋ณ€์ˆ˜๋ฅผ View์— ํ‘œ์‹œ(ViewModel โ†’ View)ํ•  ๋•Œ ์ž๋™์œผ๋กœ ์‚ฌ์šฉ๋˜๋Š” ํ•จ์ˆ˜๋‹ค. stringToInt()๋Š” ๊ทธ ๋ฐ˜๋Œ€(View โ†’  ViewModel)๋‹ค. stringToInt()๋Š” View์—์„œ ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•œ ๊ฐ’์„ ์ž๋™์œผ๋กœ ViewModel์˜ ์–ด๋–ค ๋ณ€์ˆ˜์—๊ฒŒ ์žฌํ• ๋‹นํ•œ๋‹ค. ํ”„๋กœ๊ทธ๋ž˜๋จธ๋Š” ViewModel โ†’ View์— ์‚ฌ์šฉ๋˜๋Š” ํ•จ์ˆ˜์ธ intToString()๋งŒ์„ ๋ช…์‹œ์ ์œผ๋กœ ์‚ฌ์šฉํ•œ๋‹ค. View โ†’  ViewModel์„ ๋‹ด๋‹นํ•˜๋Š” stringToInt()๋Š” ์•”์‹œ์ ์œผ๋กœ ์‹คํ–‰๋œ๋‹ค. KSP๋Š” stringToInt()๊ฐ€ intToString()์˜ ๋ฐ˜๋Œ€ ์—ญํ• ์ž„์„ ์–ด๋–ป๊ฒŒ ์‹๋ณ„ํ•˜๋Š”๊ฐ€? ๋ฐ”๋กœ @InverseMethod("stringToInt") ์–ด๋…ธํ…Œ์ด์…˜์„ ์‹๋ณ„ํ•ด์„œ๋‹ค. ๋‹ค์Œ์€ MyConverter์˜ ๋ฉ”์†Œ๋“œ๋“ค์ด View(activity_main.xml)์—์„œ ์‚ฌ์šฉ๋˜๋Š” ๋ชจ์Šต์ด๋‹ค.
 
<EditText>์— ์ˆซ์ž๋งŒ ๋ฉ๊ทธ๋Ÿฌ๋‹ˆ ํ‘œ์‹œํ•˜๊ธฐ์—” ํ—ˆ์ „ํ•  ๊ฒƒ ๊ฐ™์•„์„œ, ์ˆซ์ž ์˜ค๋ฅธ์ชฝ์— "ํšŒ"๋ฅผ ๋ถ™์ด๋Š” ๋กœ์ง๋„ ๊ตฌํ˜„ํ•ด ๋„ฃ์—ˆ๋‹ค.
 

#3-4 activity_main.xml์—์„œ Converter ์‚ฌ์šฉ

<?xml version="1.0" encoding="utf-8"?>

<layout ...>

    <data>
        <import type="com.example.twowaybindingusingconverter.MyConverter" />
        
        <variable
            name="myViewModel"
            type="com.example.twowaybindingusingconverter.MainActivityViewModel" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout ...>

        <Button ... />

        <EditText
            android:id="@+id/countText"
            ...
            android:text="@={MyConverter.intToString(countText, myViewModel.getCurrentCount())}"
            ... />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

๋จผ์ €, <EditText>์˜ android:text ์†์„ฑ์— ์žˆ๋Š” ๋ฐ”์ธ๋”ฉ ์ˆ˜์‹ @{ ... }๋ฅผ @={ ... }๋กœ ๋ณ€๊ฒฝํ•œ๋‹ค. ๊ทธ๋ž˜์•ผ๋งŒ ์–‘๋ฐฉํ–ฅ ๋ฐ์ดํ„ฐ ๋ฐ”์ธ๋”ฉ์ด ํ—ˆ์šฉ๋œ๋‹ค. <data> ํƒœ๊ทธ ์•ˆ์— MyConverter๋ฅผ importํ•œ๋‹ค. ๊ทธ๋ฆฌ๊ณ  @={ ... } ์•ˆ์— intToString()๋ฅผ ์‚ฌ์šฉํ•œ ์ฝ”๋“œ๋ฅผ ๋„ฃ๋Š”๋‹ค. ์ฐธ๊ณ ๋กœ, id๋ฅผ count_text์™€ ๊ฐ™์ด ์Šค๋„ค์ดํฌ ํ‘œ๊ธฐ๋ฒ•์œผ๋กœ ์ง€์œผ๋ฉด @={ ... } ์•ˆ์—์„œ id๊ฐ€ ์ธ์‹๋˜์ง€ ์•Š๋Š”๋‹ค. ๋”ฐ๋ผ์„œ id๋Š” ๋ฐ˜๋“œ์‹œ ์นด๋ฉœ ํ‘œ๊ธฐ๋ฒ•์„ ์‚ฌ์šฉํ•ด ์ง€์–ด์•ผ ํ•œ๋‹ค.

 

#4 BindingAdapter ๋ฐ InverseBindingAdapter ์ด์šฉ (๋ฐฉ๋ฒ• 2)

#4-1 ์‚ฌ์ „ ์ž‘์—…

KSP API์™€ kapt API๋ฅผ ํ”Œ๋Ÿฌ๊ทธ์ธ์— ์ถ”๊ฐ€ํ•œ๋‹ค. (#3-1๊ณผ #3-2 ์ฐธ์กฐ)
 

#4-2 MyBindingAdapter.kt ๋งŒ๋“ค๊ธฐ

// package com.example.twowaybindingusingcustomattributes

import android.widget.EditText
import androidx.databinding.BindingAdapter
import androidx.databinding.InverseBindingAdapter

object MyBindingAdapters {
    @BindingAdapter("android:text")
    @JvmStatic
    fun setCountText(editText: EditText, value: Int?) {
        // ์‚ฌ์šฉ์ž๊ฐ€ ์Œ์ˆ˜๋ฅผ ์ž…๋ ฅํ•  ๋•Œ, EditText.text๊ฐ€ "ํšŒ" or "-ํšŒ"์ด ๋˜๋Š” ์ˆœ๊ฐ„ "0ํšŒ"๋กœ ๊ฐฑ์‹ ๋˜์ง€ ์•Š๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•œ if๋ฌธ
        if ((editText.text.toString().replace("ํšŒ", "").toIntOrNull() ?: 0) == (value ?: 0)) {
            return
        }

        editText.setText(value?.toString() + "ํšŒ")
    }

    @InverseBindingAdapter(attribute = "android:text")
    @JvmStatic
    fun getCountText(editText: EditText): Int {
        return editText.text.toString().replace("ํšŒ", "").toIntOrNull() ?: 0
    }
}

/*
์œ„์˜ ์ฝ”๋“œ๋Š” (https://developer.android.com/topic/libraries/data-binding/two-way?hl=ko)์— ์žˆ๋Š” ์ฝ”๋“œ๋ฅผ ์ฐธ์กฐํ•œ ์ฝ”๋“œ๋‹ค.
๊ทธ ์ฝ”๋“œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

    ์˜ˆ๋ฅผ ๋“ค์–ด MyView๋ผ๋Š” ๋งž์ถค ๋ทฐ์˜ "time" ์†์„ฑ์— ์–‘๋ฐฉํ–ฅ ๋ฐ์ดํ„ฐ ๊ฒฐํ•ฉ์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ,

    @BindingAdapter("time")
    @JvmStatic fun setTime(view: MyView, newValue: Time) {
        // Important to break potential infinite loops.
        if (view.time != newValue) {
            view.time = newValue
        }
    }

    @InverseBindingAdapter("time")
    @JvmStatic fun getTime(view: MyView) : Time {
        return view.getTime()
    }
*/

Converter์™€ ๋‹ฌ๋ฆฌ BindingAdapter ๋ฐ InverseBindingAdatper๋Š” ๋ทฐ ์ž์ฒด์— ์ ์šฉ๋˜๋Š” ๋ฆฌ์Šค๋„ˆ๋‹ค. setCountText()์™€ getCountText()๋Š” android:text ์†์„ฑ์„ ๊ฐ€์ง„ ๊ฐ์ฒด๊ฐ€ ๋ณ€ํ™”๋ฅผ ๊ฐ์ง€ํ•ด ์ž๋™์œผ๋กœ ์‹คํ–‰๋œ๋‹ค. Converter์— ๋น„ํ•ด ์ผ๊ด„์ ์ธ ๋ฐ์ดํ„ฐ ๋ฐ”์ธ๋”ฉ์ด ๊ฐ€๋Šฅํ•˜๋‹ค. '์ผ๊ด„์ '์ด๋ผ๋Š” ์žฅ์ ์€, '์„ธ๋ถ€์ ' ์„ค์ •์ด ์–ด๋ ต๋‹ค๋ผ๋Š” ๋‹จ์ ์œผ๋กœ ์ž‘์šฉํ•  ์ˆ˜๋„ ์žˆ๊ฒ ์ง€๋งŒ ๋ง์ด๋‹ค. ์ฝ”๋“œ ์•„๋žซ๋ถ€๋ถ„์— ์žˆ๋Š” ์ฃผ์„์ฒ˜๋Ÿผ ์‚ฌ์šฉ์ž ์ •์˜ View์—๋„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.
 

#4-3 activity_main.xml์—์„œ BindingAdapter ๋ฐ InverseBindingAdapter ์ด์šฉ

<?xml version="1.0" encoding="utf-8"?>

<layout ...>

    <data>
        <import type="com.example.twowaybindingusingcustomattributes.MyBindingAdapters" />

        <variable
            name="myViewModel"
            type="com.example.twowaybindingusingcustomattributes.MainActivityViewModel" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout >

        <Button ... />

        <EditText
            ...
            android:text="@={(myViewModel.getCurrentCount())}"
            ... />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

<EditText>์˜ android:text ์†์„ฑ์— ์žˆ๋Š” ๋ฐ”์ธ๋”ฉ ์ˆ˜์‹ @{ ... }๋ฅผ @={ ... }๋กœ ๋ณ€๊ฒฝํ•˜๊ณ , <data> ํƒœ๊ทธ ์•ˆ์— MyBindingAdapter๋ฅผ importํ•œ๋‹ค. import๋œ ์ˆœ๊ฐ„๋ถ€ํ„ฐ activity_main.xml์—์„œ android:text ์†์„ฑ์ด ๋ณ€ํ™”๋ ๋•Œ๋งˆ๋‹ค ๋ฐ์ดํ„ฐ ๋ฐ”์ธ๋”ฉ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์— ์˜ํ•ด ๊ทธ ๋ณ€ํ™”๊ฐ€ ๊ฐ์ง€๋œ๋‹ค.

 

#5 'Built-In' BindingAdapter ๋ฐ InverseBindingAdapter ์ด์šฉ (๋ฐฉ๋ฒ• 3)

#5-1 ์‚ฌ์ „ ์ž‘์—… ํ•„์š”์—†์Œ

์‚ฌ์‹ค <TextView>์˜ android:text ์†์„ฑ์€ ์ž์ฒด์ ์œผ๋กœ '๊ธฐ๋ณธ์ ์ธ' ์–‘๋ฐฉํ–ฅ ๋ฐ์ดํ„ฐ ๋ฐ”์ธ๋”ฉ์„ ์ง€์›ํ•œ๋‹ค. ์ด๋ฏธ ๊ตฌํ˜„๋˜์–ด Built-In๋œ BindingAdapter ๋ฐ InverseBindingAdapter๊ฐ€ ์žˆ๋‹ค๋Š” ์ด์•ผ๊ธฐ๋‹ค. <TextView>์˜ ์ž์‹์ธ <EditText>์—๋„ ๋งˆ์ฐฌ๊ฐ€์ง€์ผ ๊ฒƒ์ด๋‹ค. ์ฆ‰, #4์ฒ˜๋Ÿผ MyBindingAdapter๋ฅผ ๋งŒ๋“ค ํ•„์š”๋„ ์—†๊ณ  ๊ทธ ์†์— ์žˆ๋˜ ์–ด๋…ธํ…Œ์ด์…˜ ๋ฌธ๋ฒ• ๋˜ํ•œ ์“ฐ์ง€ ์•Š์•„๋„ ๋œ๋‹ค. ๊ทธ๋ž˜์„œ #3 ๋ฐ #4์—์„œ ํ–ˆ๋˜ KSP API์™€ kapt API๋ฅผ ํ”Œ๋Ÿฌ๊ทธ์ธ์— ์ถ”๊ฐ€ํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค.
 
๋‹ค๋งŒ, Built-In๋œ ๊ฒƒ์€ ์–ด๋””๊นŒ์ง€๋‚˜ '๊ธฐ๋ณธ์ ์ธ' ์–‘๋ฐฉํ–ฅ ๋ฐ์ดํ„ฐ ๋ฐ”์ธ๋”ฉ์ด๋ผ๋Š” ๊ฒƒ์„ ๊ธฐ์–ตํ•˜์ž. #4์—์„œ์™€ ๋‹ฌ๋ฆฌ ์‚ฌ์šฉ์ž ์ •์˜ View์—๋Š” ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋‹ค. ์ œํ•œ์ ์ธ ์šฉ๋„๋กœ๋งŒ ์‚ฌ์šฉ๋œ๋‹ค๋Š” ๊ฒƒ์€ ๋‹จ์ ์ด์ง€๋งŒ, ๋ณธ ๊ฒŒ์‹œ๊ธ€์˜ ๋ฐ์ดํ„ฐ ๋ฐ”์ธ๋”ฉ ๊ตฌํ˜„ ๋ฐฉ์‹ ์ค‘์—์„œ๋Š” ๊ทธ ๊ตฌํ˜„ ๋‚œ์ด๋„๊ฐ€ ์ œ์ผ ๋‚ฎ๋‹ค๋Š” ์žฅ์ ์ด ์žˆ๋‹ค.
 

#5-2 MainActivityViewModel.kt ์ˆ˜์ •

// package com.example.twowaybindingusingbuiltinattributes

import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel

class MainActivityViewModel : ViewModel() {
    private var count: MutableLiveData<String> = MutableLiveData("0ํšŒ")

    fun getCurrentCount(): MutableLiveData<String> {
        return count
    }

    fun updateCount() {
        val valueInt = count.value?.replace("ํšŒ", "")?.toIntOrNull() ?: 0
        count.value = (valueInt + 1).toString() + "ํšŒ"
    }
}

count ๋ณ€์ˆ˜์˜ ์ œ๋„ค๋ฆญ์„ String์œผ๋กœ ๋ฐ”๊พผ๋‹ค. ๊ทธ์— ๋งž์ถฐ updateCount() ํ•จ์ˆ˜์˜ ๋‚ด์šฉ๋„ ์†๋ณธ๋‹ค. ์™œ ์ด๋Ÿฌ๋Š”๊ฑธ๊นŒ? ๋ฐ”๋กœ Built-In๋œ BindingAdapter ๋ฐ InverseBindingAdapter๊ฐ€ '๊ธฐ๋ณธ์ ์ธ' ๊ธฐ๋Šฅ๋งŒ์„ ์ œ๊ณตํ•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ์ฆ‰, ์˜ˆ๋ฅผ ๋“ค์–ด Int โ†’ String์œผ๋กœ ๋ฐ”๊พธ๊ฑฐ๋‚˜ String โ†’ Int๋กœ ๋ฐ”๊พธ๋Š” ๊ธฐ๋Šฅ์ด ์—†๋‹ค. ์˜ค์ง String โ†” String ๋งŒ ๊ฐ€๋Šฅํ•˜๋‹ค. ๊ทธ๋ž˜์„œ count์˜ ์ œ๋„ค๋ฆญ์„ ๊ทธ์— ๋งž์ถ”๋Š” ๊ฒƒ์ด๋‹ค. ๋งŒ์•ฝ, ๋ณต์žกํ•œ ๋ณ€ํ™˜์„ ๊ตฌํ˜„ํ•˜๋ ค๋ฉด ์‚ฌ์šฉ์ž ์ •์˜ ๋ฐ์ดํ„ฐ ๋ฐ”์ธ๋”ฉ ์ฆ‰ #3 ๋˜๋Š” #4์˜ ๋ฐฉ์‹์„ ์ด์šฉํ•ด์•ผ ํ•œ๋‹ค.
 

#5-3 activity_main.xml ์ˆ˜์ •

<?xml version="1.0" encoding="utf-8"?>

<layout ...>

    ...

    <androidx.constraintlayout.widget.ConstraintLayout ...>

        <Button ... />

        <EditText
            ...
            android:text="@={(myViewModel.getCurrentCount())}"
            ... />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

<EditText>์˜ android:text ์†์„ฑ์— ์žˆ๋Š” ๋ฐ”์ธ๋”ฉ ์ˆ˜์‹ @{ ... }๋ฅผ @={ ... }๋กœ ๋ณ€๊ฒฝํ•˜๋ฉด ๋์ด๋‹ค. ์ถ”๊ฐ€๋กœ count๋Š” ์ด์ œ String์ด๋‹ˆ toString()๋„ ์ œ๊ฑฐํ•œ๋‹ค.
 

#6 ์ž‘๋™ ํ™•์ธ - ์–‘๋ฐฉํ–ฅ ๋ฐ์ดํ„ฐ ๋ฐ”์ธ๋”ฉ

 

#7 ์š”์•ฝ

๋‹จ๋ฐฉํ–ฅ ๋ฐ์ดํ„ฐ ๋ฐ”์ธ๋”ฉ์„ ํ†ตํ•ด Activity๊ณผ View์˜ ๊ฒฐํ•ฉ๋„๋ฅผ ์ค„์˜€๋˜ ๊ฒƒ์ฒ˜๋Ÿผ, ์–‘๋ฐฉํ–ฅ ๋ฐ์ดํ„ฐ ๋ฐ”์ธ๋”ฉ ๋˜ํ•œ (#2-1์˜ MainActivity.kt ์ฝ”๋“œ๋ฅผ ์•”์‹œ์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•˜๋ฉด์„œ) Activity๊ณผ View์˜ ๊ฒฐํ•ฉ๋„๋ฅผ ์ค„์ธ๋‹ค.
 

#8 ์™„์„ฑ๋œ ์•ฑ

#8-1 Converter ์ด์šฉ (๋ฐฉ๋ฒ• 1)

android-practice/live-data/TwoWayBindingUsingConverter at master ยท Kanmanemone/android-practice

Contribute to Kanmanemone/android-practice development by creating an account on GitHub.

github.com

 

#8-2 BindingAdapter ๋ฐ InverseBindingAdapter ์ด์šฉ (๋ฐฉ๋ฒ• 2)

android-practice/live-data/TwoWayBindingUsingCustomAttributes at master ยท Kanmanemone/android-practice

Contribute to Kanmanemone/android-practice development by creating an account on GitHub.

github.com

 

#8-3 'Built-In' BindingAdapter ๋ฐ InverseBindingAdapter ์ด์šฉ (๋ฐฉ๋ฒ• 3)

android-practice/live-data/TwoWayBindingUsingBuiltInAttributes at master ยท Kanmanemone/android-practice

Contribute to Kanmanemone/android-practice development by creating an account on GitHub.

github.com