[Android] ViewModel ์ •๋ฆฌ

Minjun Kimยท2023๋…„ 9์›” 22์ผ
0

Android

๋ชฉ๋ก ๋ณด๊ธฐ
43/47
post-thumbnail

๐Ÿ“ SeSAC์˜ 'JetPack๊ณผ Kotlin์„ ํ™œ์šฉํ•œ Android App ๊ฐœ๋ฐœ' ๊ฐ•์ขŒ๋ฅผ ์ •๋ฆฌํ•œ ๊ธ€ ์ž…๋‹ˆ๋‹ค.


๐Ÿ’ฌ AAC ๊ฐœ์š”

โ“ AAC ๋ž€?

  • 2017 Google I/O ์—์„œ ๋ฐœํ‘œ

  • 2018 Google I/O ์—์„œ ๋ฐœํ‘œ๋œ JetPack ์œผ๋กœ ํ†ตํ•ฉ

  • Architecture Components ๊ฐ€ ๊ณต๊ฐœ๋˜๊ธฐ ์ „๊นŒ์ง€๋Š” ์•ˆ๋“œ๋กœ์ด๋“œ ์•ฑ์—์„œ ํŠน์ • Architecture ๊ฐ€๊ถŒ์žฅ๋˜์ง€ ์•Š์•˜๋‹ค.

  • ๊ฐœ๋ฐœ์ž๋“ค์—๊ฒŒ ๋”ฐ๋ผ MVP, MVC, MVVM, MVPP ๋“ฑ ๋‹ค์–‘ํ•œ ์•„ํ‚คํ…์ฒ˜๊ฐ€ ์„ ํƒ

  • ์•ˆ๋“œ๋กœ์ด๋“œ ์•ฑ์„ ์œ„ํ•œ ์•„ํ‚คํ…์ฒ˜๋ฅผ ์ •์˜ํ•˜๊ณ  ์ด๋ฅผ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ œ๊ณตํ•˜๊ณ ์ž architecture components ์„ ๊ณต๊ฐœ

๋”ฐ๋ผ์„œ JetPack ์€ ํฌ๊ฒŒ AAC ์™€ androidx ๋กœ ๋‚˜๋‰œ๋‹ค๊ณ  ๋ณผ ์ˆ˜ ์žˆ๊ฒ ๋‹ค. ์ดˆ๊ธฐ ์•ˆ๋“œ๋กœ์ด๋“œ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์€ ๋‹จ์ˆœํ•œ ๊ตฌ์กฐ๋ฅผ ๊ฐ–์กŒ์ง€๋งŒ, ์ ์  ์š”๊ตฌํ•˜๋Š” ๊ธฐ๋Šฅ์ด ๋งŽ์•„์ง์— ๋”ฐ๋ผ ๊ตฌ์กฐ๊ฐ€ ๋ณต์žกํ•ด์กŒ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๊ตฌ๊ธ€์ด ๋”ฑํžˆ ์•„ํ‚คํ…์ฒ˜ ๊ตฌ์กฐ ๋กœ ์ง€์ •ํ•œ ๊ฒƒ์ด ์—†์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ๊ฐœ๋ฐœ์ž๋“ค์€ ์ €๋งˆ๋‹ค ์ž„์˜์˜ ์•„ํ‚คํ…์ฒ˜๋ฅผ ์„ ํƒํ•˜์—ฌ ์•ฑ์„ ๊ฐœ๋ฐœํ•ด์™”๋‹ค. ๊ทธ๋Ÿฌ๋‹ค ๋“œ๋””์–ด ๊ตฌ๊ธ€์—์„œ ๊ถŒ์žฅ ์•„ํ‚คํ…์ฒ˜๋ฅผ ๋‚ด๋†“์•˜๊ณ , ์ด ์•„ํ‚คํ…์ฒ˜์˜ ๊ตฌํ˜„์„ ์ง€์›ํ•˜๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ๋ฐ”๋กœ AAC ๋ผ๊ณ  ๋ณด๋ฉด ๋˜๊ฒ ๋‹ค.

๐Ÿ“š AAC ์˜ ๊ตฌ์„ฑ ์š”์†Œ

๐Ÿ““ DataBinding

<layout>
	<data>
    	<variable name="model1" type="com.example.test_databinding.User" />
	</data>
    <TextView xmlns:android="http://schemas.android.com/apk/res/android"
    	android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text='@{"i am include xml.. binding data: "+model1.name}' />
</layout>
  • Data ๋ฅผ UI ์— ์‰ฝ๊ฒŒ Binding ํ•˜๊ธฐ ์œ„ํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ

  • findViewById() ์— ์˜ํ•œ View ๊ฐ์ฒด ํš๋“ ๋ฐ ์ด์šฉ์˜ ๋ฒˆ๊ฑฐ๋กœ์›€์„ ์ œ๊ฑฐํ•˜๊ธฐ ์œ„ํ•œ ๋ชฉ์ 

  • DI ๋กœ ์œ ๋ช…ํ•œ Butterknife ๋“ฑ์˜ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ด์šฉ๋ณด๋‹ค ํšจ์œจ์ 

  • Android Architecture Component ์˜ ๊ตฌ์„ฑ์š”์†Œ

findViewById ๋กœ ๊ฐ์ฒด ํš๋“ํ•˜๋Š” ๊ฒƒ์ด ๋ฒˆ๊ฑฐ๋กœ์›Œ ๋‚˜์˜จ ๊ธฐ์ˆ ์ด DataBinding , ๊ทธ๋ฆฌ๊ณ  DataBinding ์—์„œ ๊ฐ์ฒด ํš๋“ ์š”์†Œ๋งŒ ์ถ”์ถœํ•ด์„œ ๋‚˜์˜จ ๊ฒƒ์ด ๋ฐ”๋กœ viewBindg ์ด๋‹ค. ๋•Œ๋ฌธ์— DataBinding ์€ ์ข€ ๋” ์•„ํ‚คํ…์ฒ˜ ์š”์†Œ ์— ๊ฐ€๊น๋‹ค๊ณ  ๋ณผ ์ˆ˜ ์žˆ๊ฒ ๋‹ค. ๋ฐ์ดํ„ฐ๋Š” ์ฝ”๋“œ์—์„œ ๋จธ๋ฌผ๊ณ , ์‹ค์ œ UI ์— ์ฐํžˆ๋Š” ๋ถ€๋ถ„์€ XML ์—์„œ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ์‹์ด๋‹ค.

๐Ÿ““ Lifecycle Aware Components

  • Activity / Fragment ์˜ Lifecycle ์ฒ˜๋ฆฌ๋ฅผ ๋”ฐ๋กœ ๊ตฌ์„ฑ

  • Activity ๋Š” Lifecycle Owner ๊ฐ€ ๋˜๋ฉฐ ์ด๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ณณ์ด Lifecycle Observer

Lifecycle ๊ด€๋ จ ์ž‘์—…์„ Observer ์—์„œ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ์‹. Observer ๋Š” Owner์˜ Lifecycle ๋ณ€ํ™”๋ฅผ ๊ฐ์ง€ํ•œ๋‹ค.

์ฆ‰, Lifecycle ๊ด€๋ จ ์ฝ”๋“œ๋ฅผ ๋ถ„๋ฆฌ์‹œํ‚ด์œผ๋กœ์„œ ํŽธํ•˜๊ฒŒ ์ฝ”๋“œ ์ž‘์„ฑ์„ ํ•  ์ˆ˜ ์žˆ๊ฒŒ๋” ์ง€์›ํ•˜๋Š” ๊ฒƒ์ด ๋ชฉ์ ์ธ ์ปดํฌ๋„ŒํŠธ๋ผ๊ณ  ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

๐Ÿ““ Navigation Components

  • Navigation Component ๋Š” ํ™”๋ฉด๊ฐ„ ์ด๋™์„ ๊ฐ„ํŽธํ•˜๊ฒŒ ์ž‘์„ฑํ•˜๊ฒŒ ํ•˜๋Š” JetPack ์˜ Library ํ˜น์€ Tool

  • ํ•˜๋‚˜์˜ main activity ์— ์—ฌ๋Ÿฌ ๊ฐœ์˜ fragment ๋กœ ํ™”๋ฉด์„ ์„ค๊ณ„ํ•˜๋Š” ๊ฒƒ์„ ๋ชฉ์ ์œผ๋กœ ํ•จ

ํ™”๋ฉด์„ fragment ๋กœ ๊ตฌ์„ฑํ•  ๋•Œ ํ™”๋ฉด ์ „ํ™˜ ํ๋ฆ„๊ณผ ์ „๋‹ฌ๋˜๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ํ•œ๋ˆˆ์— ๋ณผ ์ˆ˜ ์žˆ๊ฒŒ ๋„์‹ํ™” ํ•ด์ฃผ๋Š” ์ปดํฌ๋„ŒํŠธ.

๐Ÿ““ Room

  • SOLite ์ถ”์ƒํ™” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ

  • ์•ˆ๋“œ๋กœ์ด๋“œ ์•ฑ์˜ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ด์šฉ์„ Room ์œผ๋กœ ๊ถŒ๊ณ 

Retrofit ๊ณผ ๋น„์Šทํ•œ ๋ชฉ์ ์„ ๊ฐ€์ง„๋‹ค. ์šฐ๋ฆฌ๊ฐ€ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์–ด๋…ธํ…Œ์ด์…˜๊ณผ ๋ฐ์ดํ„ฐ๋ฅผ ์ฃผ๋ฉด, Room ์—์„œ ์•Œ์•„์„œ SQL ๊ตฌํ˜„์ฒด๋ฅผ ๋งŒ๋“ค์–ด ์ค€๋‹ค. DBMS ๋ฅผ ์œ„ํ•œ ์ปดํฌ๋„ŒํŠธ.

๐Ÿ““ Paging

  • RecyclerView ์—์„œ ํŽ˜์ด์ง• ์ฒ˜๋ฆฌ๋ฅผ ์‰ฝ๊ณ  ํšจ์œจ์ ์œผ๋กœ ์ž‘์„ฑํ•˜๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ

  • ํŽ˜์ด์ง• ๋œ ๋ฐ์ดํ„ฐ์˜ ๋ฉ”๋ชจ๋ฆฌ ๋‚ด ์บ์‹ฑ

  • ์š”์ฒญ ์ค‘๋ณต ์ œ๊ฑฐ ๊ธฐ๋Šฅ์ด ๊ธฐ๋ณธ์œผ๋กœ ์ œ๊ณต

  • ๋ฐ์ดํ„ฐ์˜ ๋๊นŒ์ง€ ์Šคํฌ๋กคํ•  ๋•Œ ์–ด๋Œ‘ํ„ฐ๊ฐ€ ์ž๋™์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์š”์ฒญ

๐Ÿ““ WorkManager

  • WorkManager ๋Š” ํŠน์ • ์ž‘์—…์„ ์œ ์˜ˆ(Defer) ์ฒ˜๋ฆฌ, ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ๋ฅผ ์ œ๊ณต

  • JobScheduler, FirebaseJobDispatcher, AlarmManager ๋“ฑ์„ ํ†ตํ•ฉํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ


๐Ÿ’ฌ MVVM ๋ชจ๋ธ

  • ๊ถŒ์žฅ์‚ฌํ•ญ์€ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์€ Separation of concerns ์— ์˜ํ•ด ์ž‘์„ฑ

  • ๊ทธ๋กœ ์ธํ•œ UI ์™€ ๋ชจ๋ธ์ด ๋ถ„๋ฆฌ

AAC ์˜ ํ•ต์‹ฌ ์•„ํ‚คํ…์ฒ˜ ์ด๋‹ค.

์›๋ž˜ ์ผ๋ฐ˜์ ์œผ๋กœ ๋งŽ์ด ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋˜ ์†Œํ”„ํŠธ์›จ์–ด ๋ชจ๋ธ.
์ฃผ๋กœ ํ”„๋ก ํŠธ์—”๋“œ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์— ๋“ค์–ด๊ฐ€๋Š” ๋ชจ๋ธ์ด๋‹ค. ํ”„๋ก ํŠธ์—”๋“œ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์ฃผ๋œ ๋ชฉ์ ์€ ํ™”๋ฉด , ์ฆ‰ View ์ด๋‹ค. AAC ๋„ ์ด MVVM ์„ ๋ชฉ์ ์œผ๋กœ ํ•œ๋‹ค.

Model + View + ViewModel = MVVM

  • Model : ์‹ค์ œ ๋น„์ฆˆ๋‹ˆ์Šค ์—…๋ฌด ๋กœ์ง์ด ๊ตฌํ˜„๋˜๋Š” ๋ถ€๋ถ„, ํ˜น์€ ๋น„์ฆˆ๋‹ˆ์Šค ๋ฐ์ดํ„ฐ๊ฐ€ ํ‘œํ˜„๋˜๋Š” ๋ถ€๋ถ„ (๋ฐœ์ƒ, ๊ด€๋ฆฌ)

  • View : ํ™”๋ฉด, ํ”ํžˆ Presentation ๋กœ์ง์ด๋ผ๊ณ  ํ‘œํ˜„ํ•œ๋‹ค.

  • ViewModel : Model ๊ณผ View ์˜ ๋งค๊ฐœ ์—ญํ• ์ž. Model ์ด ๊ต์ฒด ๋˜๋”๋ผ๋„ View ์˜ํ–ฅ์ด ๋ฏธ์น˜์ง€ ์•Š๋Š”๋‹ค.

์ฆ‰, ํ™”๋ฉด์„ ๊ตฌ์„ฑํ•˜๋Š” UI, View ์™€ ๋น„์ฆˆ๋‹ˆ์Šค ์—…๋ฌด ๋กœ์ง์„ ์ œ๊ณตํ•˜๋Š” Model ์„ ๋ถ„๋ฆฌํ•œ๋‹ค๋Š” ์˜๋ฏธ.

๐Ÿ”Ž NVVM ๋ชจ๋ธ ๊ตฌ์กฐ

ํ™”๋ฉด ์ถœ๋ ฅ์€ Activity ํ˜น์€ Fragment ๊ฐ™์€ View ์—์„œ ์ฒ˜๋ฆฌํ•œ๋‹ค. ์—…๋ฌด ๋กœ์ง์€ Repository ์— ๋‹ด๊ณ  DB ๊ด€๋ จ์€ Room , ์›น ๊ด€๋ จ์€ Retrofit ์œผ๋กœ ๊ตฌํ˜„ํ•œ๋‹ค. ๊ทธ๋ฆฌ๊ณ  View ์™€ Repository ์‚ฌ์ด์— ๋งค๊ฐœ ์—ญํ• ์ž ViewModel ์„ ๋‘์–ด View ๋Š” ์ด๋ฒคํŠธ ์ž…๋ ฅ๋งŒ ๋ฐ›๊ณ , Repository ๋Š” ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ๋งŒ ์ž‘์—…ํ•œ๋‹ค. ์ฒ˜๋ฆฌ๋œ ๋ฐ์ดํ„ฐ๋Š” Repository -> ViewMoel ๋กœ ์ „๋‹ฌ๋˜์–ด LiveData ๋กœ View ์— ์ „๋‹ฌ๋œ๋‹ค. ๋˜ํ•œ Lifecycle ๊นŒ์ง€ Owner ์—์„œ ๋ถ„๋ฆฌํ•˜์—ฌ ์ œ์–ดํ•œ๋‹ค.

Activity / Fragment : View

- UI ๋ฅผ ๊ตฌ์„ฑํ•˜๊ณ  ์œ ์ €์™€ ์ƒํ˜ธ ์ž‘์šฉ. Lifecycle ๋ณ€๊ฒฝ์„ ๊ฐ์ง€

- Lifecycle ๋ณ€๊ฒฝ์ด ๋ฐœ์ƒํ•˜๋ฉด ViewModel ์— ์ „๋‹ฌ

- ViewModel ์—์„œ ์ „๋‹ฌํ•œ LiveData ์˜ ๋‚ด์šฉ์„ ํ™”๋ฉด์— ์ถœ๋ ฅ

ViewModel : View ์™€ Model ์˜ ๋ถ„๋ฆฌ ์—ญํ• ์ž

- View ์™€ Model ์„ ๋ถ„๋ฆฌ์‹œํ‚ค๊ธฐ ์œ„ํ•œ ๊ฐ€๊ต ์—ญํ• 

- View ์˜ Lifecycle ๋ณ€๊ฒฝ์„ ๊ฐ์ง€ํ•˜๊ณ  ๋ณ€๊ฒฝ์ด ๋ฐœ์ƒํ•˜๋ฉด Repository ์„ ์‹คํ–‰

Repository : ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ, ํ”ํžˆ ๋งํ•˜๋Š” 'Model'

- ๋ฐ์ดํ„ฐ์˜ ์ €์žฅ ๋ฐ ํš๋“์ด ์ฃผ ๋ชฉ์ 

- Room : ๋ฐ์ดํ„ฐ ์˜์†ํ™”

- SQLite ๋งคํ•‘ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ

๐Ÿ’ฌ ViewModel

  • MVVM ์˜ ํ•ต์‹ฌ์€ ํ™”๋ฉด์—์„œ B/L ์„ ๋ถ„๋ฆฌํ•ด์„œ ๊ฐœ๋ฐœํ•˜๋Š” ๊ฒƒ

  • Activity, Fragment ์—์„œ UI ๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ณ  ViewModel ์—์„œ B/L ์„ ์ฒ˜๋ฆฌ

  • Activity ์—์„œ onSaveInstanceState() ํ•จ์ˆ˜๋ฅผ ์ด์šฉํ•ด ์ž‘์„ฑํ•˜๋Š” Bundle ๋ฐ์ดํ„ฐ ์ €์žฅ๋„ ViewModel ๋กœ ๋Œ€์ฒด๊ฐ€ ๊ฐ€๋Šฅ

๐Ÿงท dependency

val lifecycle_version = "2.5.1"

implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version")

๐Ÿ“ ViewModel ํด๋ž˜์Šค ์ž‘์„ฑ

class MyViewModel: ViewModel() {
	val user: MutableLiveData<User>
    	get() {
        	val user = MutableLiveData<User>()
            user.postValue(User("Gildong", "hong1"))
            return user
	}
}
  • ViewModel ์ƒ์†์œผ๋กœ ์ž‘์„ฑ

๐Ÿ““ ViewModelProvider

val model = ViewModelProvider(this).get(MyViewModel::class.java)
model.user.observe(this, {
	binding.textView.text = "${it.firstName} - ${it.lastName}"
})
  • Activity ์—์„œ ViewModel ์ด์šฉ

  • ViewModelProvider ๋ฅผ ์ด์šฉํ•œ ๊ฐ์ฒด ์ƒ์„ฑ

  • ์ง์ ‘ ๊ฐ์ฒด ์ƒ์„ฑ๋„ ๊ฐ€๋Šฅ

  • ViewModelProvider ์„ ์ด์šฉํ•ด ๊ฐ์ฒด ์ƒ์„ฑ์„ ํ•ด์•ผ Activity ์ƒํƒœ ๋ณ€ํ™”์— ๋”ฐ๋ฅธ ViewModel ์ด ์žฌ์ƒ์„ฑ๋˜๋Š” ๊ฒƒ์„ ๋ง‰์„ ์ˆ˜ ์žˆ์Œ = ์ง์ ‘ ๊ฐ์ฒด ์ƒ์„ฑ ์‹œ ViewModel ์˜ ์ƒํƒœ ๋ฐ์ดํ„ฐ ์ €์žฅ ๊ธฐ๋Šฅ์„ ๋ชป ์”€

๐Ÿ“Œ delegator

val model: MyViewModel by viewModels()
  • Kotlin property delegate ์ด์šฉ

ViewModelProvider ๋กœ ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๊ฒƒ์„ ์ข€ ๋” ๋‹จ์ˆœํ™”์‹œํ‚จ delegator ๊ฐ€ ์ œ๊ณต๋œ๋‹ค. ์š”์ฆ˜ ๊ฑฐ์˜ ์ด delegator ์ด์šฉํ•˜๋Š” ์ถ”์„ธ๋ผ๊ณ  ํ•œ๋‹ค.

์ฝ”ํ‹€๋ฆฐ ํ”„๋กœ๊ทธ๋žจ์—์„œ by ๋“ค์–ด๊ฐ€๋Š” ๊ฒƒ์ด delegator ๋ฅผ ๋œปํ•œ๋‹ค.
์‚ฌ์šฉํ•˜๋ ค๋ฉด dependency ๋ฅผ ์ถ”๊ฐ€ํ•ด ์ฃผ์–ด์•ผ ํ•œ๋‹ค.

val activity_version = "1.6.1"

implementation("androidx.activity:activity-ktx:$activity_version")

๐Ÿ““ ViewModel Lifecycle

  • ViewModel Lifecycle

  • Activity Scope ๋‚ด์—์„œ Singleton

ViewModel ์˜ ๊ฐ์ฒด๋„ ๊ฒฐ๊ตญ ์•กํ‹ฐ๋น„ํ‹ฐ์—์„œ ํš๋“ํ•œ๋‹ค. ์•กํ‹ฐ๋น„ํ‹ฐ๋Š” ์ƒํƒœ ๋ณ€ํ™”(ํ™”๋ฉด ํšŒ์ „ ๋“ฑ) ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ์ข…๋ฃŒ๋˜๋ฉด์„œ ๋ชจ๋“  ๋ฐ์ดํ„ฐ๊ฐ€ ๋‚ ๋ผ๊ฐ€์ง€๋งŒ, ViewModelProvider ๋ฅผ ์‚ฌ์šฉํ•ด ์–ป์€ ViewModel ๊ฐ์ฒด๋Š” ์ข…๋ฃŒ๋˜์ง€ ์•Š๋Š”๋‹ค. ViewModel ๋‚ด์— ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ๋‹ค๋ฉด View ๋Š” ํ•ด๋‹น ๋ฐ์ดํ„ฐ๋ฅผ ์–ป์–ด์˜ค๊ธฐ๋งŒ ํ•˜๋ฉด ๋˜๊ธฐ ๋•Œ๋ฌธ์—, bundle ์„ ๋Œ€์ฒดํ•˜๋Š” ๊ธฐ๋ฒ•์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ์ด๋‹ค.

๋˜ํ•œ ํ•˜๋‚˜์˜ ์•กํ‹ฐ๋น„ํ‹ฐ์—์„œ ์—ฌ๋Ÿฌ ๋ฒˆ ์ƒ์„ฑ ํ•˜๋”๋ผ๋„ Singleton ์œผ๋กœ ์œ ์ง€๊ฐ€ ๋œ๋‹ค.

๐Ÿ““ AndroidViewModel

AndroidViewModel ์€ ViewModel ์˜ ์„œ๋ธŒ ํด๋ž˜์Šค

ViewModel()									// ๋งค๊ฐœ๋ณ€์ˆ˜ x

AndroidViewModel(Application application)	//	๋งค๊ฐœ๋ณ€์ˆ˜๋กœ context ์ด์šฉ

ViewModel ์„ ์ƒ์„ฑํ•  ๋•Œ ViewModel() ์„ ์ƒ์† ๋ฐ›์•„ ์ž‘์„ฑํ•˜๋Š” ๊ฒƒ์ด ์ผ๋ฐ˜์ ์ด๋‹ค. ์ด๋•Œ ViewModel ์˜ ์ƒ์„ฑ์ž์—๋Š” ๋งค๊ฐœ๋ณ€์ˆ˜๊ฐ€ ์—†๋‹ค. ๊ทธ๋Ÿฐ๋ฐ ๊ฐ€๋” AndroidViewModel(Application application) ์„ ์ƒ์† ๋ฐ›์•„ ViewModel ์„ ์ž‘์„ฑํ•  ๋•Œ๊ฐ€ ์žˆ๋‹ค. ์ด๋Š” ViewModel ๋‚ด์—์„œ ์ปจํ…์ŠคํŠธ ๊ฐ์ฒด, ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ฐ์ฒด๋ฅผ ์ด์šฉํ•˜๊ณ ์ž ํ•˜๋Š” ๊ฒฝ์šฐ์ด๋‹ค.

๐Ÿ““ Fragment ์—์„œ ViewModel

  • Fragment ์—์„œ ViewModel ์ด์šฉ

  • Activity ๋‚ด์— Fragment ๊ฐ€ ์—ฌ๋Ÿฌ ๊ฐœ ์žˆ๋Š” ๊ฒฝ์šฐ Fragment ๊ฐ„ ViewModel ๊ณต์œ  ํ•„์š”

ViewModel ์˜ Lifecycle ์€ Owner ๋ฅผ ๋”ฐ๋ผ๊ฐ„๋‹ค.

ํ”„๋ž˜๊ทธ๋จผํŠธ ๊ฐ๊ฐ ViewModel ์„ ์ค„ ์ˆ˜๋„ ์žˆ๊ณ , ํ•˜๋‚˜์˜ ViewModel ์„ ๊ณต์œ ํ•  ์ˆ˜๋„ ์žˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ด ๋•Œ ViewModel ์˜ Lifecycle ์€ ๋ˆ„๊ตฌ๋ฅผ Owner ๋กœ ์ง€์ •ํ–ˆ๋Š”์ง€์— ๋”ฐ๋ผ ๋‹ฌ๋ผ์ง„๋‹ค.

โ“ ViewModel ๊ณต์œ 

val model = ViewModelProvider(requireActivity()).get(MyFragmentViewModel::class.java)
  • ViewModel ์˜ Owner ๋ฅผ Activity ๋กœ ๋ณ€๊ฒฝ

ViewModelProvider ๋กœ ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•  ๋•Œ get() ํ•จ์ˆ˜๋กœ ViewModel ์ •๋ณด๋ฅผ ์ค€๋‹ค.
๊ทธ๋ฆฌ๊ณ  ViewModelProvider ์˜ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ Lifecycle Owner ๋ฅผ ์ค€๋‹ค. Owner ๋ฅผ ์•กํ‹ฐ๋น„ํ‹ฐ๋กœ ์ง€์ •ํ•˜๋ฉด ์—ฌ๋Ÿฌ ๊ฐœ์˜ ํ”„๋ž˜๊ทธ๋จผํŠธ์—์„œ ๊ณต์œ ํ•  ์ˆ˜ ์žˆ๊ณ , ํ”„๋ž˜๊ทธ๋จผํŠธ๋กœ ์ง€์ •ํ•˜๋ฉด ํ•ด๋‹น ํ”„๋ž˜๊ทธ๋จผํŠธ์˜ ๋ผ์ดํ”„์‚ฌ์ดํด๋งŒ ๊ฐ์ง€ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๊ณต์œ ๋˜์ง€ ์•Š๋Š”๋‹ค.


๐Ÿ’ฌ LiveData

๐Ÿ“ ViewModel ์†Œ์Šค ์ฝ”๋“œ

class MyViewModel: ViewModel() {
	fun someData(): String{
    	return "hello"
	}
    fun someData2(): MutableLiveData<String> {
    	val liveData = MutableLiveData<String>()
        thread {
        	SystemClock.sleep(3000)
            liveData.postValue("world")
		}
        return liveData
	}
}
  • ViewModel ์˜ ๊ฒฐ๊ณผ

  • ViewModel ์—์„œ String ๋“ฑ์˜ ๊ฒฐ๊ณผ๋ฅผ ๋ฆฌํ„ด ์‹œํ‚ฌ ์ˆ˜๋„ ์žˆ์ง€๋งŒ LiveData ์„ ๋ฆฌํ„ด ์‹œ์ผœ Observer ๋กœ ๊ฒฐ๊ณผ ์ด์šฉ

์ผ๋‹จ ๋ฆฌํ„ด๊ฐ’์„ ๋„˜๊ธด ํ›„์— Observer ๊ฐ€ ๊ฐ’ ๋ณ€๊ฒฝ์„ ๊ฐ์ง€ํ•˜๋ฉด ๋ฐ์ดํ„ฐ๋ฅผ ์ „๋‹ฌํ•œ๋‹ค.

๐Ÿ“ View ์†Œ์Šค ์ฝ”๋“œ

val observer = object : Observer<String> {
	override fun onChanged(t: String?) {
    	Log.d("kotdev", "onChanged...$t")
	}
}
model.someData2().observe(this, observer)
val liveData = model.someData2()
//.................
liveData.removeObservers(this)
  • LiveData ์˜ ๋ณ€๊ฒฝ์„ ๊ฐ์ง€ํ•˜๋Š” observer ๋Š” Observer ๋ฅผ ๊ตฌํ˜„ํ•œ ํด๋ž˜์Šค

  • LiveData ์˜ ๊ฐ’์ด ๋ณ€๊ฒฝ๋˜๋ฉด Observer ์˜ onChanged() ํ•จ์ˆ˜๊ฐ€ ์ž๋™ ํ˜ธ์ถœ

  • ๋” ์ด์ƒ ๊ฐ์ง€๊ฐ€ ํ•„์š” ์—†๋Š” ๊ฒฝ์šฐ ๋ช…์‹œ์ ์œผ๋กœ removeObservers ํ•จ์ˆ˜ ํ˜ธ์ถœ

๐Ÿ“Œ Custom LiveData

class MyLiveData : LiveData<String>() {
	fun sayHello(name: String) {
    	postValue("Hello $name")
	}
}
val liveData1 = MyLiveData()
liveData1.observe(this) {
	Log.d(
    	"kotdev", "result: $it"
	)
}
liveData1.sayHello("kotdev")
  • LiveData ๋ฅผ ์ƒ์† ๋ฐ›์•„ ์ž‘์„ฑ

  • ViewModel ์ด์™ธ์— ๋‹ค๋ฅธ ๊ณณ์—์„œ ์‚ฌ์šฉ ๊ฐ€๋Šฅ

๐Ÿงฉ ์‹ค์Šต ์˜ˆ์ œ

๐Ÿงท dependency

  • build.gradle.kts
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1")
implementation("androidx.activity:activity-ktx:$1.6.1")

๐ŸŽจ ์•กํ‹ฐ๋น„ํ‹ฐ ๋ ˆ์ด์•„์›ƒ

  • activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/holo_blue_light"
    android:gravity="center"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/resultView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textColor="@android:color/white"
        android:textSize="25dp"
        android:textStyle="bold" />

    <Button
        android:id="@+id/button"
        style="@style/Widget.MaterialComponents.Button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="call sum" />

</LinearLayout>

๐Ÿ“ฌ ViewModel

  • MyViewModel.kt
package com.kotdev99.android.c100

class MyViewModel : ViewModel() {
	var sum = 0
	fun calSum(): MutableLiveData<String> {
		val liveData = MutableLiveData<String>()
		thread {
			for (i in 1..10) {
				sum += i
				liveData.postValue(sum.toString())
				SystemClock.sleep(100)
			}
		}
		return liveData
	}
}

๐Ÿ“‚ ๋ฉ”์ธ ์†Œ์Šค ์ฝ”๋“œ

  • MainActivity.kt
package com.kotdev99.android.c100

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        val model: MyViewModel by viewModels()  // ViewModelProvider ๋ฅผ ์ด์šฉํ•œ ๊ฒƒ๊ณผ ๋™์ผ

        binding.button.setOnClickListener {
            model.calSum().observe(this) {
                binding.resultView.text = it
            }
        }
    }
}

๐Ÿ“ฒ ๊ฒฐ๊ณผ

profile
์‘์•  ๋‚˜ ์•„๊ธฐ ๋‰ด๋น„

0๊ฐœ์˜ ๋Œ“๊ธ€