디자인패턴)MVVM

소정·2023년 5월 26일
0

Kotlin

목록 보기
19/27

  • view는 뷰모델을 참조하고 뷰모델은 모델을 참조한다 (단방향 참조)
  • View와 model의 데이터를 연결해(data binding) 놓아서 데이터가 변경될 떄 변도의 처리코드 없이 view가 자동 갱신되는 특징

💡 MVVM패턴 [Model - View - ViewModel]

1) Model - 데이터 클래스
2) View - 사용자가 볼 화면 - 클릭 이벤트를 처리하여 ViewModel에게 model 제어를 요청
3) ViewModel - view와 model을 연결하는 역할, View가 연결(binding)한 데이터를 제어하도록 요청하는 코드가 있는 클래스

MVVM 사용하기

1. build.gradle에 데이터바인딩 추가 & 폴더 나누기

2. View

  • 데이터바인딩을 사용하려면 xml에 최상위 root요소가 layout이어야함
  • 최상위가 layout이 아니면 바인딩이 안됨

layout 안에 두가지 영역 존재

1.data : 레이아웃 뷰와 바인딩할 데이터 변수 선언
2.layoutview : 화면을 꾸미는 영역

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

<!--데이터 바인딩에 최상위(root) 요소-->
<layout>

    <!-- 1. 레이아웃 뷰와 바인딩할 데이터 변수 선언 -->
    <data>

    </data>

    <!--  2. 레이아웃 뷰가 여기에 놓여짐  -->
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:padding="16dp"
        tools:context=".view.MainActivity">

        <EditText
            android:id="@+id/et_name"
            android:hint="이름"
            android:inputType="text"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>

        <EditText
            android:id="@+id/et_email"
            android:hint="이메일"
            android:inputType="textEmailAddress"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>

        <Button
            android:id="@+id/btn_save"
            android:text="save Data"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>

        <Button
            android:id="@+id/btn_load"
            android:text="load Data"
            android:backgroundTint="@color/teal_700"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="80dp"/>

        <TextView
            android:id="@+id/tv_result"
            android:text="result"
            android:textStyle="bold"
            android:textColor="@color/black"
            android:padding="8dp"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>

    </LinearLayout>

</layout>


kt에서 할 일

  1. 화면 바인딩
  2. 뷰모델 객체 생성
package com.bsj0420.mvvm.view

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.databinding.DataBindingUtil
import androidx.databinding.ObservableField
import com.bsj0420.mvvm.R
import com.bsj0420.mvvm.databinding.ActivityMainBinding
import com.bsj0420.mvvm.viewmodel.MyViewModel

class MainActivity : AppCompatActivity() {

    //3. MVVM패턴 [Model - View - ViewModel]
    // : View와 model의 데이터를 연결해(data binding) 놓아서 데이터가 변경될 떄 변도의 처리코드 없이 view가 자동 갱신되는 특징
    // 1) Model - 데이터 클래스
    // 2) View - 사용자가 볼 화면 - 클릭 이벤트를 처리하여 ViewModel에게 model 제어를 요청
    // 3) ViewModel - view와 model을 연결하는 역할, View가 연결(binding)한 데이터를 제어하도록 요청하는 코드가 있는 클래스

    // view는 뷰모델을 참조하고 뷰모델은 모델을 참조한다 (단방향 참조)

    //MVVM을 위해서는 데이터바인딩 기술을 이용하여 개발하는 것이 일반적
    //데이터 바인딩은 뷰바인딩과 다르게 xml 파일에 루트요소가 <layout> 이어야만 바인딩 클래스가 만들어진다

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

        // # view 역할
        // 데이터바인딩 이용
        // 레이아웃 xml과 연결하는 바인딩클래스 [activity_main.xml -> ActivityMainBinding]
        //데이타 바인딩 기능으로 엑티비티에 setContentView를 실행
        val binding : ActivityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main)

        //Observable은 기본자료형 8개만 지원함
        //8개가 아닌 것은 전부 ObservableField<> 로 선언
//        val msg : ObservableField<String> =ObservableField("aaa")
//        binding.message = msg.get()

        // # 뷰모델 객체 생성하여 레이아웃 변수에 대입
        binding.vm = MyViewModel(this)


    }
}



3. model

데이터 클래스와 기능

UserVO

package com.bsj0420.mvvm.model

//이름 이메일 데이터 저장하는 역할의 데이터 클래스
data class UserVO(var name:String, var email:String)

UserModel

package com.bsj0420.mvvm.model

import android.content.Context
import androidx.core.content.edit

// 유저 정보(데이터)를 제어하는 기능 클래스
class UserModel constructor(val context: Context) { //데이터를 제어하기 위해서 콘텍스트 능력이 필요한 경우가 있다면 
    // 주 생성자로 전달 - 생성자 DI
    
    // 1) 데이터를 전달 받아서 SharedPreferences에 데이터 저장하는 기능
    fun saveData(name:String, email:String){

        val pref = context.getSharedPreferences("data", Context.MODE_PRIVATE)
        pref.edit {
            putString("name", name)
            putString("email", email)
        }

    }
    
    // 2) SharedPreferences에서 데이터를 읽어와서 내보내는(return) 기능
    fun loadData(): UserVO {
        val pref = context.getSharedPreferences("data", Context.MODE_PRIVATE)
        val name : String = pref.getString("name", "") as String //스트링으로 형변환
        val email : String =  pref.getString("email", "") as String

        return UserVO(name, email) //코틀린은 리턴 하나밖에 못함 그래서 모델을 사용하여 리턴
    }
    
}

4. viewmodel

  1. viewmodel에서 참조할 Model과 데이터 멤버변수 만들기

  1. 만든 참조변수로 기능 찾아오기

  2. text엔 하나밖에 못 씀 포멧을 써서 데이터 여러개 보여줄 수 있음 (string.xml 에 형식 만들어서 불러옴)

3-1) string.xml

<resources>
    <string name="app_name">MVVM</string>


    <string name="user_data">%s  -  %s</string>
</resources>

3-2) xml 화면에 적용


화면

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

<!--데이터 바인딩에 최상위(root) 요소-->
<layout>

    <!-- 1. 레이아웃 뷰와 바인딩할 데이터 변수 선언 -->
    <data>

<!--        <variable-->
<!--            name="message"-->
<!--            type="String" />-->
        <variable
            name="vm"
            type="com.bsj0420.mvvm.viewmodel.MyViewModel" />


    </data>

    <!--  2. 레이아웃 뷰가 여기에 놓여짐  -->
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:padding="16dp"
        tools:context=".view.MainActivity">

        <EditText
            android:id="@+id/et_name"
            android:hint="이름"
            android:inputType="text"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onTextChanged="@{vm::changeName}"
            />

        <EditText
            android:id="@+id/et_email"
            android:hint="이메일"
            android:inputType="textEmailAddress"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onTextChanged="@{vm::changEmail}"/>

        <Button
            android:id="@+id/btn_save"
            android:text="save Data"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onClick="@{vm::clickSave}"/>
        <!-- 등록 키워드 :: 함수 이름만 쓰면 됨 -->

        <Button
            android:id="@+id/btn_load"
            android:text="load Data"
            android:backgroundTint="@color/teal_700"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="80dp"
            android:onClick="@{vm::clickLoad}"/>

<!--        <TextView-->
<!--            android:id="@+id/tv_result"-->
<!--            android:text="@{message}"-->
<!--            android:textStyle="bold"-->
<!--            android:textColor="@color/black"-->
<!--            android:padding="8dp"-->
<!--            android:layout_width="match_parent"-->
<!--            android:layout_height="wrap_content"/>-->

        <TextView
            android:id="@+id/tv_result"
            android:text="@{String.format(@string/user_data,vm.model.name, vm.model.email)}"
            android:textStyle="bold"
            android:textColor="@color/black"
            android:padding="8dp"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>

    </LinearLayout>

</layout>

kt

package com.bsj0420.mvvm.viewmodel

import android.content.Context
import android.view.View
import androidx.databinding.ObservableField
import com.bsj0420.mvvm.model.UserModel
import com.bsj0420.mvvm.model.UserVO

class MyViewModel(context: Context) { //매게변수

    //view와 연결할 model 역할 클래스 참조변수
    var userModel : UserModel = UserModel(context)

    //값 변경이 관찰되는 특별한 데이터 멤버변수 생성 Observablexxx
    var model : ObservableField<UserVO> = ObservableField()

    //자동 호출 초기화 영역 - 주생성자의 영역
    init {
       model.set(UserVO("이름 없음","이메일 없음")) //초기값 설정
    }

    //editText에 글씨를 가지고 있을 일반 변수
    private var name : String = ""
    private var email : String = ""

    //EditText 글씨가 변경될 때 마다 반응하도록 등록한 메소드 두개
    fun changeName (s:CharSequence?, start:Int, end:Int, count:Int) {
        this.name = s.toString()
    }

    fun changEmail(s:CharSequence?, start:Int, end:Int, count:Int) {
        this.email = s.toString()
    }

    //뷰의 이벤트에 반응하여 model과 model을 제어하도록 요청하는 기능 메소드...
    fun clickSave(view:View) {
        userModel.saveData(name, email)
    }

    fun clickLoad(view:View) {
        val userVO = userModel.loadData()
        model.set(userVO)
    }

}



📢 장단점

장점

  1. MVP 처럼 뷰와 presenter가 1:1로 대응되어 있지 않아서 화면이 늘어나도 viewmodel 재사용 가능 전체 파일 개수가 준다
  2. 사용자의 이벤트를 viewmodel에서 다 하고 있음, 화면이 바뀌어도 이벤트 처리에 대한 중복 코드가 필요 없다
  3. view는 viewModel을 참조하지만 viewmodel은 view를 참조하지 않기에 view가 변경되어도 뷰모델에 영향이 없다
  4. Activity와 Fragment의 코드가 가장 간결함

단점

  1. MVVM 설계 학습이 어려움
  2. View 처리가 많아지면 viewModel의 코드가 많아져서 결국 비대해짐
profile
보조기억장치

0개의 댓글