Android(Kotlin)

심기일전 코틀린! - 앱을 만들면서 AAC 이해하기 (ViewModel, DataBinding, LiveData)

E.I.T.U 2023. 7. 26. 11:08

이미지 검색 앱을 만들어가면서 AAC에 대해 이해하는 시간을 가져보기로 했다.

 

이번 시간에는 검색어 입력 EditText와 Button을 가진 화면을 작성해보자.

 

먼저 검색어를 입력 할 EditText와 검색을 실행할 Button을 가지는 xml을 작성하자.

<!-- activity_main.xml -->

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:layout_constraintTop_toTopOf="parent"
            android:orientation="horizontal"
            android:padding="5dp">

            <EditText
                android:id="@+id/query"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:layout_marginEnd="10dp"
                android:maxLines="1"
                android:inputType="text"
                android:imeOptions="actionSearch"
                android:hint="검색어"/>

            <Button
                android:id="@+id/search"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="검색"/>

        </LinearLayout>

    </androidx.constraintlayout.widget.ConstraintLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

 

그리고 이 xml을 레이아웃으로 가지는 액티비티를 하나 생성했다.

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }

}

 

이제 검색어를 가져와서 검색을 실행하는 코드를 작성해보자.

...

//검색어
var queryString : String = ""

...

override fun onCreate(savedInstanceState: Bundle?) {

	...
    
	//query를 id로 갖는 EditText를 찾기
    val query = findViewById<EditText>(R.id.query)

    //search를 id로 갖는 Button을 찾기
    val search = findViewById<Button>(R.id.search)
    search.setOnClickListener {
        queryString = query.text.toString()    
        //TODO 검색어로 이미지 검색 실행 
    }
    
}

 

위의 코드들도 작동하는데에는 전혀 문제가 없지만 다음과 같이 개선 할 수 있을것같다.

1. DataBinding을 통해 findViewById 과정 생략하기

2. ViewModelDataBinding을 통해 검색어를 LiveData로 관리하고 양방향데이터바인딩을 적용하기

 

먼저 검색어 String과 이후 구현할 검색 결과 데이터를 관리할 ViewModel을 작성하자.

/**
 * ViewModel을 Override해도 괜찮지만
 * 이번 시간에는 Toast를 생성하기 위해 Context가 필요하여
 * AndroidViewModel을 상속받았다.
 */
class MyViewModel(application: Application) : AndroidViewModel(application) {

    private val context : Context get() = getApplication<Application>().applicationContext

	//activity_main.xml 의 query EditText와 양방향 데이터바인딩으로 묶일 검색어 LiveData
    var query : MutableLiveData<String> = MutableLiveData("")

	//activity_main.xml 의 search Button의 onClick으로 실행 될 function
    fun getData() {
        //TODO 검색어로 이미지 검색 실행
        val queryString : String? = query.value
        if (queryString != null) {
            Toast.makeText(context, "검색어 : ${queryString}", Toast.LENGTH_SHORT).show()
        }
    }

}

 

그리고 DataBinding을 적용하기 위해 activity_main.xml을 다음과 같이 수정했다.

<!-- activity_main.xml -->

<?xml version="1.0" encoding="utf-8"?>
<!-- DataBinding을 적용하기 위해 기존 레이아웃을 <layout> 으로 감싸준다 -->
<layout>

	<!-- 연결할 클래스는 <data> 태그 안에 <variable> 로 선언하여 사용할 수 있다 -->
    <data>
        <variable
            name="myViewModel"
            type="com.notegg.viewmodelexample.MyViewModel" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent">

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                app:layout_constraintTop_toTopOf="parent"
                android:orientation="horizontal"
                android:padding="5dp">

				<!-- query가 변경되면 ViewModel의 LiveData값도 갱신되어야하기 때문에 
                양방향 데이터 바인딩으로 설정 -->
                <EditText
                    android:id="@+id/query"
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:layout_marginEnd="10dp"
                    android:maxLines="1"
                    android:inputType="text"
                    android:imeOptions="actionSearch"
                    android:text="@={myViewModel.query}"
                    android:hint="검색어"/>

				<!-- function을 호출하도록 설정할 수도 있다 -->
                <Button
                    android:id="@+id/search"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="검색"
                    android:onClick="@{() -> myViewModel.getData()}"/>

            </LinearLayout>

        </androidx.constraintlayout.widget.ConstraintLayout>

    </androidx.constraintlayout.widget.ConstraintLayout>

</layout>

단순히 데이터를 표시만 하는것이 아닌, query EditText의 값이 바뀔때 LiveData도 갱신되어야 하기 때문에

단방향데이터바인딩(@{})이 아닌 양방향데이터바인딩(@={})으로 설정해주었다.

 

마지막으로 MainActivity도 다음과 같이 수정해주었다.

class MainActivity : AppCompatActivity() {

	//DataBinding 설정
    private val binding : ActivityMainBinding by lazy {
    	//setContentView를 대신하여 레이아웃을 설정해준다
        DataBindingUtil.setContentView(this, R.layout.activity_main)
    }

	/** 원래는 다음과 같이 선언해야 하지만
      * val myViewModel : MyViewModel by lazy {
      * 	ViewModelProvider(this).get()
      * }
      
      * ktx를 사용하면 by viewModels()와 같이 간단하게 선언할 수 있다.
      */
    private val myViewModel : MyViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        //activity_main.xml에 설정한 myViewModel에 this.myViewModel을 할당해준다
        binding.apply {
            myViewModel = this@MainActivity.myViewModel
            //LiveData를 사용하는 경우 잊지 말고 lifecycleOwner를 설정해주자
            lifecycleOwner = this@MainActivity
        }
        binding.query.setOnEditorActionListener { _, _, _ ->
            myViewModel.getData()
            true
        }
    }

}

 

 

참고하면 좋은 문서

Android KTX

https://developer.android.com/kotlin/ktx?hl=ko

 

Android KTX  |  Kotlin  |  Android Developers

컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. Android KTX는 Android Jetpack과 기타 Android 라이브러리에 포함된 Kotlin 확장 프로그램 세트입니다. KTX 확장 프로그

developer.android.com