From b84fcc98e68d4c3db25db590b81edbf1a89d1f49 Mon Sep 17 00:00:00 2001 From: hwannow Date: Thu, 3 Oct 2024 13:26:58 +0900 Subject: [PATCH 1/3] =?UTF-8?q?feat(MainActivity,=20MainViewModel,=20ViewM?= =?UTF-8?q?odelFactory):=20=EC=A0=84=EC=B2=B4=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 8 +++ .../com/alom/androidstudy1/MainActivity.kt | 49 +++++++++++++++---- .../com/alom/androidstudy1/MainViewModel.kt | 30 ++++++++++++ .../alom/androidstudy1/ViewModelFactory.kt | 14 ++++++ app/src/main/res/layout/activity_main.xml | 38 ++++++++++++-- gradle/libs.versions.toml | 6 +++ 6 files changed, 133 insertions(+), 12 deletions(-) create mode 100644 app/src/main/java/com/alom/androidstudy1/MainViewModel.kt create mode 100644 app/src/main/java/com/alom/androidstudy1/ViewModelFactory.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index e86faf7..0e9904a 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -7,6 +7,10 @@ android { namespace = "com.alom.androidstudy1" compileSdk = 34 + buildFeatures { + viewBinding = true + } + defaultConfig { applicationId = "com.alom.androidstudy1" minSdk = 26 @@ -42,4 +46,8 @@ dependencies { testImplementation(libs.junit) androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) + + implementation(libs.lifecycle.viewmodel.ktx) + implementation(libs.lifecycle.livedata.ktx) + implementation(libs.lifecycle.runtime.ktx) } \ No newline at end of file diff --git a/app/src/main/java/com/alom/androidstudy1/MainActivity.kt b/app/src/main/java/com/alom/androidstudy1/MainActivity.kt index c1bcbbf..affefff 100644 --- a/app/src/main/java/com/alom/androidstudy1/MainActivity.kt +++ b/app/src/main/java/com/alom/androidstudy1/MainActivity.kt @@ -1,20 +1,51 @@ package com.alom.androidstudy1 import android.os.Bundle -import androidx.activity.enableEdgeToEdge +import android.util.Log +import android.widget.Toast import androidx.appcompat.app.AppCompatActivity -import androidx.core.view.ViewCompat -import androidx.core.view.WindowInsetsCompat +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle +import com.alom.androidstudy1.databinding.ActivityMainBinding +import kotlinx.coroutines.launch class MainActivity : AppCompatActivity() { + private lateinit var binding:ActivityMainBinding + private lateinit var mainViewModel: MainViewModel override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - enableEdgeToEdge() - setContentView(R.layout.activity_main) - ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets -> - val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()) - v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom) - insets + binding = ActivityMainBinding.inflate(layoutInflater) + setContentView(binding.root) + + val sharedPreference = getSharedPreferences("alom", MODE_PRIVATE) + + // 인자값으로 sharedPreference를 넘겨 주기 위해 factory 사용 + val factory = ViewModelFactory(sharedPreference) + + mainViewModel = ViewModelProvider(this, factory).get(MainViewModel::class.java) + + lifecycleScope.launch { + lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { + launch { + mainViewModel.currentMemo.collect{ memo -> + binding.etMemo.setText(memo) + Log.d("MainActivity", "output memo: $memo") + } + } + } + } + + binding.btnSave.setOnClickListener { + val newMemo = binding.etMemo.text.toString().trim() + if(newMemo.isEmpty()){ + Toast.makeText(this, "저장할 메모를 입력해 주세요", Toast.LENGTH_SHORT).show() + } + else { + mainViewModel.updateValue(newMemo) + Toast.makeText(this, "저장됨", Toast.LENGTH_SHORT).show() + } } } } \ No newline at end of file diff --git a/app/src/main/java/com/alom/androidstudy1/MainViewModel.kt b/app/src/main/java/com/alom/androidstudy1/MainViewModel.kt new file mode 100644 index 0000000..0a5068e --- /dev/null +++ b/app/src/main/java/com/alom/androidstudy1/MainViewModel.kt @@ -0,0 +1,30 @@ +package com.alom.androidstudy1 + +import android.annotation.SuppressLint +import android.content.SharedPreferences +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch + +class MainViewModel(private val sharedPreference: SharedPreferences): ViewModel() { + private var _currentMemo = MutableStateFlow("") + + val currentMemo: StateFlow + get() = _currentMemo.asStateFlow() + + init { + _currentMemo = MutableStateFlow(sharedPreference.getString("memo", "").toString()) + } + + + @SuppressLint("CommitPrefEdits") + fun updateValue(input: String) { + viewModelScope.launch { + _currentMemo.emit(input) + sharedPreference.edit().putString("memo", input).apply() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/alom/androidstudy1/ViewModelFactory.kt b/app/src/main/java/com/alom/androidstudy1/ViewModelFactory.kt new file mode 100644 index 0000000..c2d38fe --- /dev/null +++ b/app/src/main/java/com/alom/androidstudy1/ViewModelFactory.kt @@ -0,0 +1,14 @@ +package com.alom.androidstudy1 + +import android.content.SharedPreferences +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider + +class ViewModelFactory(private val sharedPreference: SharedPreferences): ViewModelProvider.Factory { + override fun create(modelClass: Class): T { + if (modelClass.isAssignableFrom(MainViewModel::class.java)) { + return MainViewModel(sharedPreference) as T + } + throw IllegalArgumentException("ViewModel class not found") + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 86a5d97..ac18dc2 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -8,12 +8,44 @@ tools:context=".MainActivity"> + + + + + app:layout_constraintEnd_toEndOf="parent" + android:layout_marginBottom="10dp" + android:text="저장" + android:textColor="@color/white" + android:background="#9281CD" + /> \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e0e8c2c..5bfcba7 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -9,6 +9,8 @@ appcompat = "1.7.0" material = "1.12.0" activity = "1.9.2" constraintlayout = "2.1.4" +lifecycleVersion = "2.8.4" + [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } @@ -20,6 +22,10 @@ material = { group = "com.google.android.material", name = "material", version.r androidx-activity = { group = "androidx.activity", name = "activity", version.ref = "activity" } androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" } +lifecycle-viewmodel-ktx = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-ktx", version.ref = "lifecycleVersion"} +lifecycle-livedata-ktx = { group = "androidx.lifecycle", name = "lifecycle-livedata-ktx", version.ref = "lifecycleVersion"} +lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleVersion"} + [plugins] android-application = { id = "com.android.application", version.ref = "agp" } jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } From ec41411930b0e16e15c953b9d5a70480964cec8a Mon Sep 17 00:00:00 2001 From: hwannow Date: Thu, 3 Oct 2024 14:09:02 +0900 Subject: [PATCH 2/3] =?UTF-8?q?feat:=20Repository=20=ED=8C=A8=ED=84=B4=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=3F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/alom/androidstudy1/MainActivity.kt | 5 +++-- .../java/com/alom/androidstudy1/MainViewModel.kt | 8 +++++--- .../main/java/com/alom/androidstudy1/Repository.kt | 9 +++++++++ .../java/com/alom/androidstudy1/RepositoryImpl.kt | 14 ++++++++++++++ .../com/alom/androidstudy1/ViewModelFactory.kt | 4 ++-- 5 files changed, 33 insertions(+), 7 deletions(-) create mode 100644 app/src/main/java/com/alom/androidstudy1/Repository.kt create mode 100644 app/src/main/java/com/alom/androidstudy1/RepositoryImpl.kt diff --git a/app/src/main/java/com/alom/androidstudy1/MainActivity.kt b/app/src/main/java/com/alom/androidstudy1/MainActivity.kt index affefff..234d2ef 100644 --- a/app/src/main/java/com/alom/androidstudy1/MainActivity.kt +++ b/app/src/main/java/com/alom/androidstudy1/MainActivity.kt @@ -21,8 +21,9 @@ class MainActivity : AppCompatActivity() { val sharedPreference = getSharedPreferences("alom", MODE_PRIVATE) - // 인자값으로 sharedPreference를 넘겨 주기 위해 factory 사용 - val factory = ViewModelFactory(sharedPreference) + // viewModel의 인자값으로 repository를 넘겨 줌 + val repository = RepositoryImpl(sharedPreference) + val factory = ViewModelFactory(repository) mainViewModel = ViewModelProvider(this, factory).get(MainViewModel::class.java) diff --git a/app/src/main/java/com/alom/androidstudy1/MainViewModel.kt b/app/src/main/java/com/alom/androidstudy1/MainViewModel.kt index 0a5068e..c65cc63 100644 --- a/app/src/main/java/com/alom/androidstudy1/MainViewModel.kt +++ b/app/src/main/java/com/alom/androidstudy1/MainViewModel.kt @@ -9,14 +9,16 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch -class MainViewModel(private val sharedPreference: SharedPreferences): ViewModel() { +class MainViewModel(private val repository: Repository): ViewModel() { private var _currentMemo = MutableStateFlow("") val currentMemo: StateFlow get() = _currentMemo.asStateFlow() init { - _currentMemo = MutableStateFlow(sharedPreference.getString("memo", "").toString()) + viewModelScope.launch { + _currentMemo.emit(repository.getMemo()) + } } @@ -24,7 +26,7 @@ class MainViewModel(private val sharedPreference: SharedPreferences): ViewModel( fun updateValue(input: String) { viewModelScope.launch { _currentMemo.emit(input) - sharedPreference.edit().putString("memo", input).apply() + repository.setMemo(input) } } } \ No newline at end of file diff --git a/app/src/main/java/com/alom/androidstudy1/Repository.kt b/app/src/main/java/com/alom/androidstudy1/Repository.kt new file mode 100644 index 0000000..51e6a69 --- /dev/null +++ b/app/src/main/java/com/alom/androidstudy1/Repository.kt @@ -0,0 +1,9 @@ +package com.alom.androidstudy1 + +import android.content.SharedPreferences +import kotlinx.coroutines.flow.MutableStateFlow + +interface Repository { + suspend fun getMemo(): String + suspend fun setMemo(input: String) +} \ No newline at end of file diff --git a/app/src/main/java/com/alom/androidstudy1/RepositoryImpl.kt b/app/src/main/java/com/alom/androidstudy1/RepositoryImpl.kt new file mode 100644 index 0000000..ddda8db --- /dev/null +++ b/app/src/main/java/com/alom/androidstudy1/RepositoryImpl.kt @@ -0,0 +1,14 @@ +package com.alom.androidstudy1 + +import android.content.SharedPreferences +import kotlinx.coroutines.flow.MutableStateFlow + +class RepositoryImpl(private val sharedPreference: SharedPreferences): Repository { + override suspend fun getMemo(): String { + return sharedPreference.getString("memo", "").toString() + } + + override suspend fun setMemo(input: String) { + sharedPreference.edit().putString("memo", input).apply() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/alom/androidstudy1/ViewModelFactory.kt b/app/src/main/java/com/alom/androidstudy1/ViewModelFactory.kt index c2d38fe..2a66f79 100644 --- a/app/src/main/java/com/alom/androidstudy1/ViewModelFactory.kt +++ b/app/src/main/java/com/alom/androidstudy1/ViewModelFactory.kt @@ -4,10 +4,10 @@ import android.content.SharedPreferences import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider -class ViewModelFactory(private val sharedPreference: SharedPreferences): ViewModelProvider.Factory { +class ViewModelFactory(private val repository: Repository): ViewModelProvider.Factory { override fun create(modelClass: Class): T { if (modelClass.isAssignableFrom(MainViewModel::class.java)) { - return MainViewModel(sharedPreference) as T + return MainViewModel(repository) as T } throw IllegalArgumentException("ViewModel class not found") } From 24ac700177cdb797be252f2b88eb76607b5d00b0 Mon Sep 17 00:00:00 2001 From: hwannow Date: Sun, 6 Oct 2024 20:14:57 +0900 Subject: [PATCH 3/3] =?UTF-8?q?feat(MainViewModel):=20=EC=BD=94=EB=A3=A8?= =?UTF-8?q?=ED=8B=B4=EC=9C=BC=EB=A1=9C=20=EB=B9=84=EB=8F=99=EA=B8=B0?= =?UTF-8?q?=EC=A0=81=EC=9C=BC=EB=A1=9C=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20?= =?UTF-8?q?=EC=9D=BD=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 3 +++ .../java/com/alom/androidstudy1/MainViewModel.kt | 13 ++++++++++--- gradle/libs.versions.toml | 4 ++++ 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 0e9904a..3fddd23 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -50,4 +50,7 @@ dependencies { implementation(libs.lifecycle.viewmodel.ktx) implementation(libs.lifecycle.livedata.ktx) implementation(libs.lifecycle.runtime.ktx) + + testImplementation(libs.spring.boot.starter.test) + testImplementation(libs.mockk) } \ No newline at end of file diff --git a/app/src/main/java/com/alom/androidstudy1/MainViewModel.kt b/app/src/main/java/com/alom/androidstudy1/MainViewModel.kt index c65cc63..eee4c0b 100644 --- a/app/src/main/java/com/alom/androidstudy1/MainViewModel.kt +++ b/app/src/main/java/com/alom/androidstudy1/MainViewModel.kt @@ -1,13 +1,14 @@ package com.alom.androidstudy1 import android.annotation.SuppressLint -import android.content.SharedPreferences import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext class MainViewModel(private val repository: Repository): ViewModel() { private var _currentMemo = MutableStateFlow("") @@ -16,8 +17,12 @@ class MainViewModel(private val repository: Repository): ViewModel() { get() = _currentMemo.asStateFlow() init { + viewModelScope.launch { - _currentMemo.emit(repository.getMemo()) + val memo = withContext(Dispatchers.IO) { + repository.getMemo() + } + _currentMemo.emit(memo) } } @@ -26,7 +31,9 @@ class MainViewModel(private val repository: Repository): ViewModel() { fun updateValue(input: String) { viewModelScope.launch { _currentMemo.emit(input) - repository.setMemo(input) + withContext(Dispatchers.IO) { + repository.setMemo(input) + } } } } \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5bfcba7..97afca5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -10,6 +10,7 @@ material = "1.12.0" activity = "1.9.2" constraintlayout = "2.1.4" lifecycleVersion = "2.8.4" +mockk = "1.12.5" [libraries] @@ -26,6 +27,9 @@ lifecycle-viewmodel-ktx = { group = "androidx.lifecycle", name = "lifecycle-view lifecycle-livedata-ktx = { group = "androidx.lifecycle", name = "lifecycle-livedata-ktx", version.ref = "lifecycleVersion"} lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleVersion"} +mockk = { module = "io.mockk:mockk", version.ref = "mockk" } +spring-boot-starter-test = { module = "org.springframework.boot:spring-boot-starter-test" } + [plugins] android-application = { id = "com.android.application", version.ref = "agp" } jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }