2021. 11. 3. 07:47ㆍDiary/300~400
오늘 4시간정도자고
지금 출근중이다.
오늘은 무조건 제목에 적힌
내용들을 조금이라도
정리해서 올려보려고한다.
오후6시30분쯤에 퇴근하고
오후9시에 집에서 글쓰고
바로 잘 예정이다.
현재 시간 오후 5시 44분...
퇴근은 오후 5시에 했는데
방보려고 부동산 투어중이다.
오늘은 그래도 어제보다는
일찍 집에 들어가고싶다.
현재 시간 오후 9시!
집에 도착했다!
코드리뷰하면서 배웠던 것들
복기하고 밥먹고 자야겠다.
3일 내내 4시간씩만 잤더니
눈이 너무 따갑다..
[ GitHub - 전체 소스 코드 ]
https://github.com/KwonGeneral/Kotlin_Basic.git
GitHub - KwonGeneral/Kotlin_Basic: 코틀린 기초 문법
코틀린 기초 문법. Contribute to KwonGeneral/Kotlin_Basic development by creating an account on GitHub.
github.com
< 완전성 & 독립성 & 목적성 & MVVM >
위의 완전성, 독립성, 목적성
3가지는 결국 같은 말이다.
완전성을 갖춘다는 것은
독립적이다는 뜻이고
이는 목적성을 가진다는 것이다.
패키지는 도메인과 같다.
특정 도메인은
특정 기능만을 담당해야한다.
예를 들자면,
어떤 클래스나 함수의 목적이
표시라고 한다면,
그 클래스나 함수는 표시에 해당하는
모든 기능을 가지고 있어야 한다.
그리고 언제 어디서든 호출해서
누구든지 사용할 수 있어야한다.
내가 개인적으로 하는 프로젝트들은
무지성으로 해도 괜찮다.
다만, 협업으로 하는 프로젝트들은
아키텍쳐 패턴, 디자인 패턴 등등
실무를 위해서 익혀야한다.
이는 유지 보수와도 크게 연관이 있다.
MVVM 아키텍쳐 패턴을 사용하는 이유도
결국 재활용을 용이하게 하기 위해서이고
이는 협업과 유지 보수에 중요하다.
MVVM에서 핵심은 각자의 역할이
확실하다는 점인데,
그 중에서도 뷰는 뷰의 역할만을
해야한다는게 와닿았다.
Model, View, ViewModel
이렇게 나뉘어져 있는데,
여기서 DB 관련된건 Model에서
전부 처리해주고
그 외적인건 ViewModel에서
처리한다고 생각하면 편하다.
View에서는 최대한 변수나
아이템을 가진다던지 그러한 것들을
자제하는게 좋고
아예 로직에 관한 시나리오 자체를
몰라야한다.
즉, 쉽게 말해서
View는 전등이고
Model, ViewModel은 스위치다.
View는 그저 스위치 On, Off상태에 따라
전등을 키고 끄고만 제어한다.
Model과 ViewModel은 어떠한 상황에서
스위치를 On, Off를 시킬지를 제어한다.
[ 소스 코드 ]
잘못된 방법은 주석을 참고하길 바란다.
class UserAdapter constructor(var context:Context, var items:ArrayList<UserData>, var adapterBtnClick: onAdapterBtnClick): RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val inflater = LayoutInflater.from(context)
val itemView = inflater.inflate(R.layout.fragment_user_read_item, parent, false)
return VH(itemView)
}
@SuppressLint("NotifyDataSetChanged")
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val vh: VH = holder as VH
with(vh.itemView) {
// Null 체크 후, 명시적 선언을 위해 item으로 치환해서 사용.
items[position]?.let { item ->
read_user_name.text = item.name
read_user_age.text = item.age
read_user_gender.text = item.gender
read_user_address.text = item.address
read_user_phone.text = item.phone
// 수정 버튼 클릭 시, 프레그먼트 전환.
read_update_btn.setOnClickListener {
// (context as UserActivity).changeFragment(UPDATE, item.id)
// 위의 코드는 종속성을 가지게 된다.
// 다른 액티비티에서 이 어댑터를 사용하지 못하게 된다.
// 이 어댑터는 항상 어디에서든지 사용할 수 있어야한다.
// 위의 문제점을 해결하기 위해서는
// 인터페이스를 사용하여 독립적으로 만들고 나서,
// 데이터 처리는 외부에서 하게끔 만들어야한다.
adapterBtnClick.onUpdate(item.id!!)
}
// 삭제 버튼 클릭 시, items 새로고침 및 DB 데이터 삭제.
read_delete_btn.setOnClickListener {
// 위와 같은 이유와 더불어
// 아래처럼 모델에 바로 접근하는 것은 위험하다.
// UserDataBase.getInstance(context)?.userDao()?.userDelete(UserData(
// item.id,
// item.name,
// item.age,
// item.gender,
// item.address,
// item.phone
// ))
adapterBtnClick.onDelete(item.id!!)
items.removeAt(position)
notifyDataSetChanged()
}
}
}
}
interface onAdapterBtnClick {
fun onUpdate(id: Int)
fun onDelete(id: Int)
}
override fun getItemCount(): Int {
return items.size
}
class VH constructor(itemView: View) : RecyclerView.ViewHolder(itemView){
}
}
< Fragment newInstance & 싱글톤 >
프레그먼트가 내려갔다가 재생성될 때,
해당 프레그먼트는 파라미터를 받지 않고,
기본적인 프레그먼트 객체만 생성해서
재생성이 된다.
즉, 파라미터가 존재하는데 프레그먼트가
재생성되면 프레그먼트는 파라미터를
불러오지 않기 때문에,
프레그먼트 내부에서 파라미터를 사용하는
로직이 존재한다면, 앱이 죽는다.
이는 빌드 에러가 아닌 런타임 에러여서
실행 중간에 앱이 죽기 때문에 신경써야한다.
이를 해결하기 위해서 Bundle을 사용한다.
Bundle은 프레그먼트가 생성될 때,
뒤에서 붙기 때문에
Bundle 값을 사용하면 앱이 죽지 않는다.
newInstance를 사용하는 이유는
필수 파라미터를 구분하기 위해서인데
이는 목적성, 독립성을 가진다.
[ 소스 코드 ]
잘못된 방법은 주석을 참고하길 바란다.
companion object {
// Thread를 동기화 하기 위해서 Synchronized를 사용한다.
// Thread는 Synchronized 메소드에 들어가기 위해 Lock을 얻고 메소드가 끝이나면 Lock을 반환한다.
// 예를 들면, 자칫 여러 인스턴스를 생성될 수 있는 경우에 하나만 생성하도록 하기 위해 Synchronized 데코레이터를 통해 방지한다.
/*
@Synchronized
fun getInstance(context: Context): UserDataBase? {
val instance = UserDataBase?.let {
Room.databaseBuilder(
context.applicationContext,
UserDataBase::class.java,
"board_db"
)
.fallbackToDestructiveMigration() // 스키마(=테이블 설계 구조) 버전 변경 가능
.allowMainThreadQueries() // Main Thread에서 DB에 IO(Input/Output)를 가능하게 함
.build()
}
return instance
}
*/
// 위와 같은 구조는 싱글톤 패턴이라 볼 수 없다.
// 호출할 때 마다, 새로운 instance를 계속 생성하는 것은 싱글톤 패턴이 아니다.
// 아래가 getInstace와 맞는 싱글톤 패턴이다.
// 하나의 instance만을 메모리에 올려서 사용하는 것이 싱글톤 패턴이다.
val instance :UserDataBase? = null
@Synchronized
fun getInstance(context: Context): UserDataBase? {
instance?.let {
return it
}
return Room.databaseBuilder(
context.applicationContext,
UserDataBase::class.java,
"board_db"
)
.fallbackToDestructiveMigration() // 스키마(=테이블 설계 구조) 버전 변경 가능
.allowMainThreadQueries() // Main Thread에서 DB에 IO(Input/Output)를 가능하게 함
.build()
}
}
// 싱글톤 패턴을 사용해 getInstance() 함수 작성
companion object {
var obModel: ObserverViewModel? = null
/*
fun getInstance(): ObserverViewModel {
obModel?.let {
return obModel!!
}
return obModel!!
}
*/
// 위 처럼 return 값을 잘못 설정하면 앱이 죽거나
// 반대로 ObserverViewModel() 만을 리턴하면 옵저버 값이 바뀌지 않는다.
// 아래와 같이 사용해야 작동한다.
fun getInstance(): ObserverViewModel {
obModel?.let {
return obModel!!
}
obModel = ObserverViewModel()
return obModel!!
}
}
< LiveData >
라이브데이터는
라이프사이클에 자동으로 맞춰서 작동한다.
즉, 퍼포먼스는 떨어질지언정 안전하다.
라이브데이터는 옵저버패턴이라고 보면 된다.
즉, 특정 변수를 관찰하는 것이다.
[ 소스 코드 ]
잘못된 방법은 주석을 참고하길 바란다.
package com.example.kotlin_basic.User
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import com.example.kotlin_basic.R
import com.example.kotlin_basic.User.adapter.UserAdapter
import com.example.kotlin_basic.User.contain.Define.Companion.CREATE
import com.example.kotlin_basic.User.contain.Define.Companion.READ
import com.example.kotlin_basic.User.contain.Define.Companion.SEARCH
import com.example.kotlin_basic.User.contain.Define.Companion.UPDATE
import com.example.kotlin_basic.User.model.UserData
import com.example.kotlin_basic.User.model.UserDataBase
import kotlinx.android.synthetic.main.activity_user.*
// 유저 생성, 삭제, 검색 기능과
// 유저 리스트 목록 (조회) 기능을 만들어보자.
class UserActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_user)
// fragment에서는 lifrecycleowner를 viewLifecycleOwner로 주고
// activity에서는 this로 매개변수 전달한다.
ObserverViewModel.getInstance().screen_change_tag.observe(this, { ob ->
// Log.d("TEST", "프레그먼트 전환 옵저버 : $ob")
when(ob) {
UPDATE -> return@observe
else -> changeFragment(ob)
}
})
ObserverViewModel.getInstance().update_get_user_id.observe(this, { id ->
changeFragment(UPDATE, id)
})
// Create Fragment 전환
user_create_btn.setOnClickListener {
ObserverViewModel.getInstance().screen_change_tag.value = CREATE
// changeFragment(CREATE)
}
// Read Fragment 전환
user_read_btn.setOnClickListener {
ObserverViewModel.getInstance().screen_change_tag.value = READ
// changeFragment(READ)
}
// Update Fragment 전환
user_search_btn.setOnClickListener {
ObserverViewModel.getInstance().screen_change_tag.value = SEARCH
// changeFragment(SEARCH)
}
}
fun changeFragment(type:String?, item_id:Int? = null) {
type?.let { ty ->
checkFragment(ty, item_id)
}
}
private fun checkFragment(type:String?, item_id:Int? = null) {
supportFragmentManager?.beginTransaction()?.let { ft ->
type?.let { ty ->
when(ty) {
CREATE -> {
UserCreateFragment()?.run {
ft.replace(R.id.main_frag, this, ty).commit()
}
}
READ -> {
UserReadFragment()?.run {
ft.replace(R.id.main_frag, this, ty).commit()
}
}
UPDATE -> {
/*
UserUpdateFragment.newInstance()?.run {
item_id?.let {
arguments = Bundle().apply {
putString("id", item_id.toString())
}
ft.replace(R.id.main_frag, this, ty).commit()
}
}
*/
// 위의 코드는 newInstance를 사용할 필요가 없다.
// newInstance를 사용해서 목적성을 표현하기 위해
// 필수 파라미터를 넣는다.
// 프레그먼트가 재 생성될 때, 파라미터는 받지 않고
// 기본 객체만을 가지고 생성되기 때문에, 런타임 에러가 발생한다.
// 번들은 프레그먼트가 생성되면서 붙기 때문에,
// 런타임 에러를 방지할 수 있다.
UserUpdateFragment.newInstance(item_id)?.run {
ft.replace(R.id.main_frag, this, ty).commit()
}
}
SEARCH -> {
UserSearchFragment()?.apply {
ft.replace(R.id.main_frag, this, ty).commit()
}
}
else -> {
Log.d("TEST", "! Type Error !")
}
}
}
}
}
}
< 코루틴의 사용처 >
코루틴의 사용처는 많지만
위의 라이브데이터와 연관지어서 말하자면
현재 내가 개발한 앱은
Room DB(로컬)를 기반으로 작동한다.
만약에 로컬 DB가 아닌 서버 DB로
변경해야 하는 상황이 온다면,
REST API를 사용해야 하는데,
이 때, 라이브데이터를 사용하면 괜찮지만
만약 단순 조회기능이여서
라이브데이터를 사용하지 않고
Room DB에서 바로 데이터를 가져와서
뷰에 그리고 있었다면?
이를 REST API로 변경해서 실행하면
값이 안떨어졌기 때문에 뷰가 안그려진다.
즉, 이 때 코루틴을 사용해야한다.
코루틴을 미리 사용해두면
나중에 유지보수 차원에서 편리하다.
< XML 디자인 호환성 >
androidx.appcompat.widget.AppCompat*
위의 위젯이 호환성이 좋다.
< 추가 공부 사항 >
1. getItemViewType에 대해서 알아보자.
2. Gson을 자주 사용해보자.
후.. 정말 대단한 사수분을 만나서
많이 배우고 있다.
가르쳐주신 부분의 50%도 소화하지
못했지만, 계속 복기하고 실습하면서
최대한 빠르게 이해하고 습득하려고 한다.
그리고 인정을 받고 실무에 빨리 투입되고 싶다.
적어도 배움에 있어서
부끄럽고 민망하지만
모르는건 모른다고 말하고 있다.
" 얘는 이런것도 모르나? "
라고 생각할까봐 두려움은 있다.
다만, 잘 알지도 못하는데 대충 넘기면
분명 나중에 더 크게 실수할 것 같다.
차라리 지금 욕을 먹더라도
최대한 많이 배워서 실무에서 실수하지 말자.
라고 생각하고 있다.
원래 나는 사수가 없어도
잘 할 수 있다고 생각했다.
하지만, 좋은 사수를 만나니까
배울점이 굉장히 많아서 너무 좋다.
지금은 라이브 데이터를 많이 사용하고 있다.
그리고 완전성 & 목적성 & 독립성에 맞춰
MVVM 아키텍처 패턴을 연습 중이다.
내가 확실하게 이해되면
다음에 설명할 때는,
더 쉽게 설명할 수 있을 것 같다.
오늘도 의식의 흐름대로 글을 마친다.
'Diary > 300~400' 카테고리의 다른 글
311일차 - 현재 상황 및 React, Firebase 연동 계획 (0) | 2021.11.05 |
---|---|
310일차 - 볼만한 유튜브 강의 영상 (0) | 2021.11.04 |
308일차 - 현재 상황 및 공부 예정 목록 (0) | 2021.11.02 |
307일차 - 가독성과 안전한 코드, also, with, let, run, apply, Room (0) | 2021.11.01 |
306일차 - 간단한 조사(AI, DL 설명 및 사용 기술) (0) | 2021.10.31 |