아래는 코모스튜디오가 직접 만든 무료 앱이에요(한 번만 봐주세요 ^^)
2개의 탭이 있으며,
좌측에는 나의 정원, 우측에는 식물 리스트가 있다.
여러 종류의 식물들이 있는 식물 리스트에서 식물을 하나 선택하면 그 식물의 Detail View로 간다.
DetailView에서 나의 정원으로 담기를 누를 경우 선택된 식물이 나의 정원에 담기는데, 이 일련의 과정(MVVM)들의 흐름을 알아본다.
View
layout 은 데이터 바인딩을 사용하고
- viewModel을 통해 View를 업데이트하고,
- callback을 등록해서 add 버튼에 바로 동작하게 한다
frament_plant_detail.xml
<layout 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">
<data>
<import type="garden.data.Plant"/>
<variable
name="viewModel"
type="garden.viewmodels.PlantDetailViewModel" />
<variable
name="callback"
type="garden.PlantDetailFragment.Callback" />
</data>
callback 은 아래 Fab버튼을 눌렀을 때 반응하도록 onClick 리스너에 등록해준다
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab"
style="@style/Widget.MaterialComponents.FloatingActionButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/fab_margin"
//callback 등록
android:onClick="@{() -> callback.add(viewModel.plant)}"
android:tint="@android:color/white"
app:shapeAppearance="@style/ShapeAppearance.Sunflower.FAB"
app:isFabGone="@{viewModel.isPlanted}"
app:layout_anchor="@id/appbar"
app:layout_anchorGravity="bottom|end"
app:srcCompat="@drawable/ic_plus" />
View -담기 버튼의 동작을 위한 작업
ViewModel 생성(참고: ViewMode 주입)과 callback생성
- Safe Argument로 받아온 plandId로 ViewModel을 생성한다.
- 참고(Safe Argument)
DataBinding에 callback을 연결
- 싱글턴 callback object를 생성한다
- also, let, apply 등의 코틀린 스코프 함수는 여기를 참고
PlantDetailFragment.kt
class PlantDetailFragment : Fragment() {
private val args: PlantDetailFragmentArgs by navArgs()
private val plantDetailViewModel: PlantDetailViewModel by viewModels {
InjectorUtils.providePlantDetailViewModelFactory(requireActivity(), args.plantId)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val binding = DataBindingUtil.inflate<FragmentPlantDetailBinding>(
inflater, R.layout.fragment_plant_detail, container, false
).apply {
viewModel = plantDetailViewModel
lifecycleOwner = viewLifecycleOwner
//binding에 callback을 연결
//singtone object를 생성해서 callback으로 연결한다.
callback = object : Callback {
//add interface 구현
override fun add(plant: Plant?) {
plant?.let {
hideAppBarFab(fab)
//ViewModel에서 나의 정원으로 식물을 담는 작업을 시작한다
plantDetailViewModel.addPlantToGarden()
Snackbar.make(root, R.string.added_plant_to_garden, Snackbar.LENGTH_LONG)
.show()
}
}
}
...
interface Callback {
fun add(plant: Plant?)
}
담기 버튼을 누르면 ViewModle의 addPlantToGarden()이 호출되도록 callBack을 구성하였다.
ViewModel -담기 버튼을 누르면
뷰 페이저 좌측은 담긴 식물, 우측에는 담을 식물들인데, 좌측 담긴 식물에 담기기 위해서는 정원 데이터 객체를 생성해서 삽입해야 한다.
- 이 작업은 코루틴 launch(리턴 없음 VS async는 Deferred<T> 객체 리턴/await()) 함수에서 비동기적으로 진행되는데,
- 이 작업 영역(launch)을 아래 appPLantToGarden() 함수 내에서 viewModelScpoe를 적용하면, ViewModel이 없어질 경우 이 작업을 자동으로 Cancel 되게 할 수도 있다.
class PlantDetailViewModel(
plantRepository: PlantRepository,
private val gardenPlantingRepository: GardenPlantingRepository,
private val plantId: String
) : ViewModel() {
val isPlanted = gardenPlantingRepository.isPlanted(plantId)
val plant = plantRepository.getPlant(plantId)
fun addPlantToGarden() {
//비동기 코루틴
viewModelScope.launch {
gardenPlantingRepository.createGardenPlanting(plantId)
}
}
}
VM-M 정원 데이터 객체 생성과 삽입
Repository에서
- 선택된 식물(Plant Table)을 담을 정원 객체를 생성하고(foreginkey로 Plant Table과 연결),
- 나의 정원(GardenPlanting Table)에 담기
GardenPlantingRepository.kt
suspend fun createGardenPlanting(plantId: String) {
//1
val gardenPlanting = GardenPlanting(plantId)
//2
gardenPlantingDao.insertGardenPlanting(gardenPlanting)
}
1. GardenPlanting(plantId)
- 정원(GardenPlanting Table)에 담길 식물의 정원식물 id, 심은 날짜, 마지막으로 물준날의 정보를 현재로 초기화해서 새 Data 객체를 생성
- 나의 정원에 담을 식물의 Id(GardenPLanting Table)와 식물 리스트의 Id(Plant Table)를 foreignKey에 연결한다.
GardenPlanting.kt
@Entity(
tableName = "garden_plantings",
foreignKeys = [
ForeignKey(entity = Plant::class, parentColumns = ["id"], childColumns = ["plant_id"])
],
indices = [Index("plant_id")]
)
data class GardenPlanting(
@ColumnInfo(name = "plant_id") val plantId: String,
/**
* Indicates when the [Plant] was planted. Used for showing notification when it's time
* to harvest the plant.
*/
@ColumnInfo(name = "plant_date") val plantDate: Calendar = Calendar.getInstance(),
/**
* Indicates when the [Plant] was last watered. Used for showing notification when it's
* time to water the plant.
*/
@ColumnInfo(name = "last_watering_date")
val lastWateringDate: Calendar = Calendar.getInstance()
) {
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "id")
var gardenPlantingId: Long = 0
}
Plant.kt
@Entity(tableName = "plants")
data class Plant(
@PrimaryKey @ColumnInfo(name = "id") val plantId: String,
val name: String,
val description: String,
val growZoneNumber: Int,
val wateringInterval: Int = 7, // how often the plant should be watered, in days
val imageUrl: String = ""
) {
2. gardenPlantingDao.insertGardenPlanting(gardenPalnting)
- 2. 새로 만든 Data 객체(GardenPlanting)를 Repository에서 실제 Room Database에 삽입한다.
GardenPlantingRepository.kt
suspend fun createGardenPlanting(plantId: String) {
//1. 데이터 객체생성
val gardenPlanting = GardenPlanting(plantId)
//2. Room DB에 삽입
gardenPlantingDao.insertGardenPlanting(gardenPlanting)
}
- @Insert Room Database에 삽입 하기
GardenPlantingDao.kt
@Insert
suspend fun insertGardenPlanting(gardenPlanting: GardenPlanting): Long
Summary
View는 ViewModel과 데이터 바인딩으로 연결되며
- View는 Callback으로 ViewModel과 연결되어 자동으로 Repository와 Model 간에 데이터 관리가 이루어진다.
M-V-VM에서 ViewModel과 Model 간의 연결은 싱글톤 @Volatile Repository 가 담당을 한다.
- Repository에서 Dao를 통해 Model에 데이터를 저장하고 꺼내온다
Room Database 관련 작업은 코루틴을 통해 비동기적으로 한다.
- 완료된 작업은 View의 구독자에게 LiveData 및 Observe로 자동 알림이 가며, 자동으로 업데이트될 수 있다.
Android AAC JetPack Sunflower
이 글은 코모가 구글 안드로이드 Sunflower를 디비보기 한 것입니다.
' [안드로이드 공부] > 선플라워 디비보기' 카테고리의 다른 글
[Sunflower 디비보기] BindingAdapter로 View visible/gone (0) | 2020.07.30 |
---|---|
[Sunflower 디비보기] Activity, Fragment, Navigation 시작 (0) | 2020.07.29 |
[Sunflower 디비보기] 데이터 바인딩으로 ImageView 에 Glide (0) | 2020.07.29 |
[Sunflower 디비보기] 데이터 바인딩과 HtmlCompat.fromhtml (0) | 2020.07.29 |
[Sunflower 디비보기] ViewModel 주입(초기화) 과정 (0) | 2020.07.28 |
모든 게시물은 코모스튜디오의 소유이며, 무단 복제 수정은 절대 불가입니다. |
퍼가실 경우 댓글과 블로그 주소를 남기고 해당 게시물에 출처를 명확히 밝히세요. |