💡 MVVM패턴 [Model - View - ViewModel]
1) Model - 데이터 클래스
2) View - 사용자가 볼 화면 - 클릭 이벤트를 처리하여 ViewModel에게 model 제어를 요청
3) ViewModel - view와 model을 연결하는 역할, View가 연결(binding)한 데이터를 제어하도록 요청하는 코드가 있는 클래스
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>
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)
}
}
데이터 클래스와 기능
package com.bsj0420.mvvm.model
//이름 이메일 데이터 저장하는 역할의 데이터 클래스
data class UserVO(var name:String, var email:String)
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) //코틀린은 리턴 하나밖에 못함 그래서 모델을 사용하여 리턴
}
}
만든 참조변수로 기능 찾아오기
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>
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)
}
}