안드로이드

고성욱·2023년 3월 17일
0

안드로이드

목록 보기
1/26

데이터 바인딩과 MVVM(kotlin)

GitHub - WrappingorNot/MVVM_BeatBox

1. 다른 아키텍쳐의 필요성

MVC 아키텍쳐

  • 규모가 작고 간단한 앱에는 좋음
  • 새로운 기능을 추가하기 쉬움
  • 앱의 동적인 부분을 싑게 알 수 있음
  • 프로젝트 초기단계에 확고한 개발 기반을 만들어줘서 앱을 빨리 개발할 수 있다.

MVC 아키텍쳐의 문제점

  • 프로젝트가 커지면 문제가 발생한다.
  • 작성과 이해가 어려워짐 - 새로운 기능 추가, 결함을 해결하는데 시간이 걸린다.
  • 거대한 클래스 보단 작업을 분할하여 협업해야 한다.

→ MVVM(모델-뷰-뷰-뷰모델)과 같은 아키텍쳐를 사용하는 것, 작업 분할은 직접 구현해야함.

MVVM 에서는 뷰 와 밀접한 컨트롤러 코드를 레이아웃 파일로 옮길수 있기 때문 동적인(변하는데이터 처리하는) 컨트롤러의 코드의 일부를 뷰모델 클래스에 넣어서 앱의 테스트와 검증도 쉽게 가능

2. MVVM 뷰모델 vs Jetpack ViewModel

Jetpack ViewModel

  • 액티비티나 프래그먼트의 생명주기에 걸쳐 데이터를 유지하고 관리하는 클래스

    MVVM 뷰모델

  • 개념적인 아키텍쳐의 일부분

3. MVVM모델을 사용하여 application 생성하기

플러그인 import 하는 방식이 안드로이드 스튜디오에서 변경 되었다. 모르면 아래 링크 참고하면 된다.

데이터 바인딩의 몇가지 장점중 최고

  • findViewById(..)를 호출 하지 않고도 뷰를 사용할수 있게 해준다. (자동으로 뷰에 데이터를 넘겨줌)
  • id "org.jetbrains.kotlin.kapt" version "1.7.21" apply false , id 'kotlin-kapt'입력plugin에
  • gradle(Moduule)에 아래 코드를 입력 한다.
dataBinding{
enabled = true
}

kotlin-kapt 플러그인을 적용하면 데이터 바인딩에서 코틀린의 애노테이션을 처리할 수 있다.

<layout xmlns:android="http://schemas.android.com/apk/res/android">
<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/recycler_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>
</layout>

위의 태그는 이 레이아웃에 데이터 바인딩을 한다는 것을 나타낸다.

레이아웃에 이 태그가 있으면 데이터 바인딩 라이브러리가 바인딩 클래스 를 자동으로 생성한다.

→ setContentView(Int)를 사용해서 뷰를 인플레이트 하는 대신에 ActivityMainbinding의 인스턴스를 인플레이트 한다.

Untitled

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

        val binding: ActivityMainBinding =
            DataBindingUtil.setContentView(this, R.layout.activity_main)

        binding.recyclerView.apply{
layoutManager= GridLayoutManager(context, 3) //recyclerview가 한 행에 세 개의 격자를 가진다
}
}

    private inner class SoundHolder(private val binding: ListItemSoundBinding):
            RecyclerView.ViewHolder(binding.root)
}//list_item_sound와 연결되는 뷰홀더를 생성함 

//Sound 객체를 생성하는 List 생성 하기 
class BeatBox(private val assets:AssetManager) {

    val sounds : List<Sound>

    init{
        sounds = loadSounds()
    }

    private fun loadSounds(): List<Sound> {
        val soundNames: Array<String>
        try {
            soundNames = assets.list(SOUND_FOLDER)!!

        } catch (e: Exception) {
            Log.e(TAG, "Could not list assets", e)

            returnemptyList()
        }

        val sounds =mutableListOf<Sound>()
        soundNames.forEach{filename->
val assetPath =  "$SOUND_FOLDER/$filename"
            val sound = Sound(assetPath)
            sounds.add(sound)
}
return sounds
    }
}

에셋을 추가 해서 아래의 사진과 같잉 데이터 바인딩을 실시 한다.

Untitled

보통 하나의 클래스는 한 가지의 책임만 가지게 하는것이 기본 원리

모델은 앱이 작동하는 방법을 나타낸다

컨트롤러는 모델과 뷰를 중재 하면서 앱의 데이터를 보여주는 방법을 결정한다

뷰는 화면에 데이터를 보여준다

그러므로 MVVM 모델로 사용하기 위해서는 뷰 모델이 필요하다.

IMG_7053.heic

위의 모델이 MVVM 모델이라고 한다.

보여줄 데이터를 형식화 하기 위해 MVC의 컨트롤러 클래스가 런타임시 시에 했던 대부분의 일을 뷰모델이 담당한다.

레이아웃에서 위젯들을 데이터와 바인딩하던 일을 뷰모델이 하게 된다.

컨트롤러(액티비티 나 프래그먼트)는 데이터 바인딩과 뷰모델을 초기화 하고 연결하는 일을 맡게 된다.

4. ViewModel 생성

class SoundViewModel {

    var sound: Sound? = null
        set(sound) {
field= sound
        }

    val title: String?
        get() = sound?.name
}

ViewModel Binding(list_item_sound)

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>
        <variable
            name="viewModel"
            type="com.example.mvvm_beatbox.SoundViewModel" />
    </data>

    <Button
        android:layout_width="match_parent"
        android:layout_height="120dp"
        android:layout_marginStart="5dp"
        android:layout_marginEnd="5dp"
       />

</layout>
  • 정의된 viewModel은 list_item_sound.xml의 자동 생성된 바인딩 클래스인 ListItem SoundBinding의 속성이 된다. ListItemSoundBinding에서 viewModel 속성을 사용하여 뷰모델인 SoundViewModel과 바인딩 할 수 있다.
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>
        <variable
            name="viewModel"
            type="com.example.mvvm_beatbox.SoundViewModel" />
    </data>

    <Button
        android:layout_width="match_parent"
        android:layout_height="120dp"
        android:layout_marginStart="5dp"
        android:text="@{viewModel.title}"
        android:layout_marginEnd="5dp"
       />

</layout>
  • @{ }은 바인딩 연산자로 내부에서는 간단한 코틀린 표현식을 사용할 수 있다. (함수 연쇄 호출이나, 수식 등)

BeatBox_databinding.png

Screenshot_20221215_141827.png

RecyclerVie의 각 항목(위에서는 버튼) 데이터를 갖는 Soundholer에 다음 코드를 추가 한다.

init {
    binding.viewModel= SoundViewModel()
}
fun bind(sound: Sound){
    binding.apply{
viewModel?.sound = sound
        executePendingBindings()
}
  • SoundViewModel 인스턴스를 생성하고 이것의 탐조를 바인딩 클래스인 ListItemSoundBinding의 viewModel 속성에 설정 한다. 그리고 바인딩 함수인 bind(…)를 추가한다.

  • init 초기화 블록에서 뷰모델 인스턴스를 생성, 바인딩 클래스의 viewModel 속성을 초기화

  • 바인딩 함수인 bind(Sound)에서는 viewModel속성을 변경한다

  • executePendingBindings() 호출할 필요는 없으나, RecyclerView에 포함된 바인딩 데이터 변경, 뷰의 빠른 속도로 변경해야 한다. → 빠른 반응을 위해 executePendingBindings() 함수 사용

  • onBindViewHolder(…)에서 bind(Sound) 함수를 호출하여 뷰모델의 각 Sound 인스턴스를 SoundHolder 인스턴스와 연결한다.

관찰 가능한 데이터

import androidx.databinding.Bindable

class SoundViewModel : BaseObservable() {

    var sound: Sound? = null
        set(sound) {
field= sound
            notifyChange()
        }

    @get:Bindable
    val title: String?
        get() = sound?.name
}
  • 화면을 가로로 돌렸을때 데이터를 제대로 찾지 못해 뷰가 이상해지는 현상 발생
  • 이유는 → 레이아웃 SoundHolder.bind(Sound)함수 내부에서 SoundViewModel의 Sound객체를 변경 했을을 때 알 수 있는 방법이 없기 때문이다. MVVM 아키텍쳐 에서는 이 방법을 찾는것이 중요

해결방법 (위의 코드 포함)

  • 뷰모델이 SoundViewModel을 BaseObservable의 서브 클래스로 선언한다.

  • SoundViewModel의 바인딩되는 속성에 @Bindable 애노테이션을 지정한다.

  • 바인딩되는 속성의 값이 변경될 때마다 notifyChange()또는 notifyPropertyChanged(Int)를 호출한다.

    #안드로이드 #디자인패턴 #개발

profile
안드로이드, 파이썬 개발자

0개의 댓글