์ด๋ฒ์ ํ์๊ฐ ๊ณต๋ถํด๋ณธ ๊ฒ์ MVVM์ด๋ค.
MVVM์ View - Model - ViewModel์ ์ฝ์์ธ๊ฑด ๋ค ์๊ณ ์๋ ์ฌ์ค์ด๋ ๊ทธ๋ฅ ๋๊ธฐ๊ณ (?)
ํ์๋ ์ค๋์ ๋ถํฐ ์ฌ์ฉํด์๋ ์๋๋ก์ด๋ ์ํคํ ์ฒ์ด์ง๋ง
์ฐ๋ ์ด์ ์ ๊ฐ ํ์ผ๋ค์ด ์ ํํ ๋ฌด์์ ์ํ ํ์ผ๋ค์ธ์ง ์ ์์ง ๋ชปํ๊ธฐ์
์ด๋ฒ ๊ธฐํ์ ์ ๋ฐ๋ก ์์๋ณด๊ณ ์ ํ๋ค.
1. MVVM์ ๊ตฌ์กฐ
MVVM์ ์ด๋ฆ์์๋ ์ ์ ์๋ฏ์ด View, Model, ViewModel๋ก ์ด๋ฃจ์ด์ ธ ์๋ค.
- View: ์ด๋ฒคํธ ๋ฐ์, liveData ๊ฐ์งํด ๋ทฐ์ ์ถ๋ ฅํ๋ค(
์ถ๋ ฅ์ธ๊ฐ). - Model: ์ค์ง์ ์ธ ๋ฐ์ดํฐ๋ฅผ ๋ค๋ฃจ๋ ๊ณณ(DB, Api ๋ฑ์ ๋ฐ์ดํฐ๋ฅผ ์ ์(?)ํ๋ค.)
- ViewModel: ํ๋ก์ ํธ์ ๋น์ฆ๋์ค ๋ก์ง์ ๊ด๋ฆฌํ๋ค(Api ํธ์ถ, DBํธ์ถ ์ด๋ฐ๊ฑฐ).
์ฃผ์ํ ์ ์ View์ Model์ ์์กด์ฑ์ด ์์ด์ผ ํ๋ค.
๊ทธ๋ผ ๊ถ๊ธํ ๊ฒ์ด๋ค
์ MVVM์ ์จ์ผ ํ๋๊ฐ? ์ฐ๋ฉด ํ์ผ๋ ๋ง์ด ๋ง๋ค์ด์ผ ํ๊ณ ์กด๋๊ท์ฐฎ์์ง๊ฒ ๊ฐ์๋ฐ...
์๋๋ฉด(์ดํด๋ฅผ ์ํด MVC์ MVP ์ํคํ ์ฒ๋ฅผ ์๋ก ๋ค์ด๋ณด๊ฒ ๋ค)
- MVC๋ ์ด๋ณด์๋ฅผ ์ํ ์ฌ์ด ํ์ผ ๊ตฌ์กฐ์ด์ง๋ง ์ฝ๋๋ฅผ ๋ชจ๋ activity์ ๊ฝ์ ๋ฃ๊ธฐ(?) ๋๋ฌธ์ ์ฑ์ด ๋ฌด๊ฑฐ์์ง ์ ์๋ค(MVVM์ activity์ ๋ชจ๋ ์ฝ๋๋ฅผ ๋ฃ๋ ๋ฐฉ์์ด ์๋ ์ญํ ๋ณ๋ก ํ์ผ ๊ตฌ์กฐ๋ฅผ ๋ถ๋ฆฌํด๋จ๋ค).
- MVP๋ View์ Model์ด Presenter๋ฅผ ํตํด์๋ง ๋์ํ๊ฒ ํ๋ ๋์์ธ ํจํด์ด์ง๋ง ํ๋ก์ ํธ๊ฐ ์ปค์ง๋ฉด View์ Model์ด Presenter์์ ์์กด์ฑ์ด ์ปค์ง๋ค๋ ๋จ์ ์ด ์๋ค(์์กด์ฑ์ด ์ปค์ง๊ฒ ๋๋ฉด ์ฑ์ ์ ์ง๋ณด์ ํ๊ธฐ๊ฐ ๋งค์ฐ ๊ป๋๋ฌ์์ง๋ค.).
MVVM์ View์ Presenter๊ฐ 1:1์ ๊ด๊ณ๋ฅผ ๊ฐ์ง๋๊ฒ ์๋, ํ๋์ ViewModel์ ์ฌ๋ฌ activity๊ฐ ์ฌ์ฉํ ์ ์๋ค๋ ์ฅ์ ์ด ์๋ค(1:ๅค ๊ด๊ณ).
์ด์ MVVM์ ์ฌ์ฉํด์ผ ํ๋ ์ด์ ๋ ์์์ผ๋ ๊ฐ๋จํ๊ฒ ๊นํ๋ธ open api ์์ ๋ฅผ ํตํด ๊ณต๋ถํด๋ณด๋๋ก ํ์.
ํ์ผ ๊ตฌ์กฐ
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:gravity="center"
tools:context=".view.MainActivity"
android:orientation="vertical">
<ImageView
android:layout_width="250dp"
android:layout_height="250dp"
android:src="@drawable/github_icon"
android:layout_gravity="center"/>
<EditText
android:id="@+id/github_user_name"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:layout_marginTop="15dp"
android:layout_marginStart="35dp"
android:layout_marginEnd="35dp"
android:hint="github username" />
<Button
android:id="@+id/result_btn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="find"
android:backgroundTint="@color/black"
android:layout_marginEnd="55dp"
android:layout_marginStart="55dp"
android:layout_marginTop="40dp"/>
</LinearLayout>
2. activity_user_info.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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".view.UserInfoActivity">
<ImageView
android:id="@+id/profile_img"
android:layout_width="140dp"
android:layout_height="140dp"
android:layout_marginStart="20dp"
android:layout_marginTop="40dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="@+id/profile_img"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/profile_img"
app:layout_constraintTop_toTopOf="@+id/profile_img">
<TextView
android:id="@+id/user_name_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="name(subname)"
android:textSize="23sp"
android:textStyle="bold"
android:textColor="@color/black"/>
<TextView
android:id="@+id/follow_info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="follower: 47, following: 49"
android:textSize="17sp"
android:autoSizeTextType="uniform"/>
</LinearLayout>
<LinearLayout
android:id="@+id/linearLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:orientation="vertical"
app:layout_constraintStart_toStartOf="@+id/profile_img"
app:layout_constraintTop_toBottomOf="@+id/profile_img">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/company_icon" />
<TextView
android:id="@+id/company_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginStart="15dp"
android:text="company"
android:textSize="17sp" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="15dp"
android:orientation="horizontal">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/baseline_location_on_24" />
<TextView
android:id="@+id/location_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginStart="15dp"
android:text="location"
android:textSize="17sp" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="15dp"
android:orientation="horizontal">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/link_icon" />
<TextView
android:id="@+id/link_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginStart="15dp"
android:text="link"
android:textSize="17sp" />
</LinearLayout>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
1. models
GithubUser.kt
import com.google.gson.annotations.SerializedName
data class GithubUser(
@SerializedName("login") val username: String,
@SerializedName("name") val name: String,
@SerializedName("avatar_url") val avatarUrl: String,
@SerializedName("company") val company: String,
@SerializedName("blog") val blog: String,
@SerializedName("location") val location: String,
@SerializedName("followers") val followers: Int,
@SerializedName("following") val following: Int
)
GithubService.kt
import retrofit2.Call
import retrofit2.http.GET
import retrofit2.http.Path
interface GithubService {
@GET("users/{owner}")
fun getUser(
@Path("owner") owner: String
): Call<GithubUser>
}
GithubObject.kt
import com.google.gson.GsonBuilder
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
object GithubObject {
private val gson = GsonBuilder()
.setLenient()
.create()
private val okHttp = OkHttpClient.Builder()
.addInterceptor(HttpLoggingInterceptor())
.build()
private val retrofit = Retrofit.Builder()
.baseUrl("https://api.github.com/")
.client(okHttp)
.addConverterFactory(GsonConverterFactory.create(gson))
.build()
val githubService: GithubService = retrofit.create(GithubService::class.java)
}
model ๊ณ์ธต์์ ํ์๋ ๋ถ๋ฌ์ฌ ๋ฐ์ดํฐ๋ค, api service, api builder๋ฅผ ๋ง๋ค์๋ค.
2. ViewModel
import android.util.Log
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.example.day2.model.GithubObject
import com.example.day2.model.GithubUser
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
class GithubViewModel: ViewModel() {
private val _getGithub = MutableLiveData<GithubUser>()
val getGithub: LiveData<GithubUser> = _getGithub
fun getGithubUser(userName: String) {
GithubObject.githubService.getUser(userName).enqueue(object : Callback<GithubUser> {
override fun onResponse(call: Call<GithubUser>, response: Response<GithubUser>) {
if (response.isSuccessful) { // 400์๋ฌ๊ฐ ์๋ธ
_getGithub.value = response.body()
} else Log.d("TAG", "onResponse error: $response")
}
override fun onFailure(call: Call<GithubUser>, t: Throwable) {
Log.d("TAG", "onFailure response fail: $call")
Log.e("TAG", "onFailure response fail: ${t.printStackTrace()}", t.cause)
}
})
}
}
viewmodel ๊ณ์ธต์์๋ retrofit ํธ์ถ์ ๋ก์ง์ ์์ฑํ๋ค.
3. View
MainActivity.kt
import android.content.Intent
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.example.day2.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.resultBtn.setOnClickListener {
startActivity(Intent(this, UserInfoActivity::class.java)
.putExtra("name", binding.githubUserName.text.toString()))
}
}
}
UserInfoActivity.kt
import android.os.Bundle
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import coil.load
import coil.transform.CircleCropTransformation
import com.example.day2.databinding.ActivityUserInfoBinding
import com.example.day2.viewmodel.GithubViewModel
class UserInfoActivity : AppCompatActivity() {
private val viewModel by viewModels<GithubViewModel>()
private lateinit var binding: ActivityUserInfoBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityUserInfoBinding.inflate(layoutInflater)
setContentView(binding.root)
val name = intent.getStringExtra("name")
if (name != null) {
viewModel.getGithubUser(name)
viewModel.getGithub.observe(this) { data ->
binding.profileImg.load(data.avatarUrl) {
transformations(CircleCropTransformation())
}
binding.userNameText.text = "${data.username}(${data.name})"
binding.followInfo.text = "followers: ${data.followers}, following: ${data.following}"
binding.companyText.text = data.company
binding.locationText.text = data.location
binding.linkText.text = data.blog
}
}
}
}
view ๊ณ์ธต์์๋ viewmodel์์ ์์ฑํ ๋ก์ง์ ์ด์ฉํด ๋ทฐ๋ฅผ ํ๋ฉด์ ์ถ๋ ฅํ๋ค.
์ ๋ฆฌ
์ด๋ฒ์ MVVM์ ๋ค์ ๊ณต๋ถํด ๋ณด๋ฉด์ ๊ฐ๊ฐ์ ๊ณ์ธต๋ค์ ์ญํ ์ ๋ ์ ํํ๊ฒ ์๊ฒ ๋์๋ค.
'๐ฑ| Android > ๐ | ๊ธฐ๋ก' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[Android] MVI ํจํด์ ๋ฌด์์ผ๊น? (0) | 2023.08.21 |
---|---|
[Android, Kotlin] Android clean architecture ํ๋ก์ ํธ์์ api ์๋ฌ ํธ๋ค๋งํ๊ธฐ (0) | 2023.08.01 |
[Android, Kotlin] Zxing ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก QR์ฝ๋ ์ค์บํ๊ธฐ (4) | 2023.03.11 |
[Android, Kotlin] android custom dialog ๋ง๋ค๊ธฐ (5) | 2022.11.20 |
[Android, Kotlin] Android์์ Apollo๋ฅผ ์ด์ฉํ GraphQL์ฌ์ฉํ๊ธฐ (0) | 2022.10.23 |