마스터Q&A 안드로이드는 안드로이드 개발자들의 질문과 답변을 위한 지식 커뮤니티 사이트입니다. 안드로이드펍에서 운영하고 있습니다. [사용법, 운영진]

이건 버그라고 봐도 무방할까요?

0 추천

데이터베이스 - Room 쓸때 DAO에서 반드시 LiveData를 반환해야하나요? - 안드로이드 Q&A (masterqna.com)

앞전에 원인을 모르는 Room 에러가 떴다고 질문드렸었는데, 그때 해결 못하고

결국 뭐가 원인인지 찾으려고 commit을 앞으로 뭐가문제인지 찾으려고 되돌려서 다시 진행했습니다.

앞 커밋에서 진행한게 필요없는 클래스 삭제 및 주석쳤던 코드삭제 필요없는 코드 삭제 같은걸 진행했었고 추가로 Navigation Destination 같은것을 추가삭제했습니다.

다른데는 문제가 없었고 Destination의 추가삭제하면서 이번에 또 똑같은 에러가 발생하는걸 알수 있었습니다. 이유는 모르겠습니다.

Bottom Navigation에서 Navigation을 변경하려고 Destination을 새로 추가했습니다.,

 

좀 더 자세히 설명 드리면

 

 

------------------------------------------------------------------------------------------

에러 (이전글 복사)

처음에는 Entity 클래스에서 

error: Cannot find setter for field.

    private final long workoutId = 0L;

이러한 에러를 띄웠습니다.

건드리지도 않은 코드의 에러가 왜 뜨는지 도저히 이해가 안갔지만 

android - Cannot find setter for field - using Kotlin with Room database - Stack Overflow

참고해서 val을 var로 변경했습니다.

그후 실행하니

error: Not sure how to convert a Cursor to this method's return type (java.lang.Object).

    public abstract java.lang.Object getWorkoutList(@org.jetbrains.annotations.NotNull()

 

이러한 에러를 띄웠습니다.

검색 결과 

android - Room "Not sure how to convert a Cursor to this method's return type": which method? - Stack Overflow

여기서 suspend를 제거하라해서 했습니다. LiveData도 사용하라고 한것 같지만 이전에는 없어도 잘 동작되어서 일단 진행했습니다.

error: Type of the parameter must be a class annotated with @Entity or a collection/array of it.

    kotlin.coroutines.Continuation<? super kotlin.Unit> continuation);

 

---------------------

Workout

@Entity
data class Workout(
    @PrimaryKey(autoGenerate = true)
    val workoutId: Long = 0,
    val title: String = "",
    val memo: String = "",
)

 

WorkoutListDao

@Dao
interface WorkoutListDao {
    @Query("SELECT * FROM WorkoutList")
    suspend fun getWorkoutList() : WorkoutList

    @Insert
    suspend fun insertWorkoutList(workoutList: WorkoutList)
}

 

 

DetailViewModel

class DetailViewModel(application: Application, title: String) : ViewModel() {
    private val workoutDao = DetailDatabase.getDatabase(application).workoutDao()
    private val repository: WorkoutRepository = WorkoutRepository(workoutDao, title)
    private val _items: MutableLiveData<List<WorkoutSetInfo>> = MutableLiveData()
    val items: LiveData<List<WorkoutSetInfo>>
        get() = _items

    fun changeUnit(unit: WorkoutUnit) { 
        repository.changeUnit(unit)
        _items.postValue(repository.getList())
    }

    fun addSet() { 
        viewModelScope.launch(Dispatchers.IO){
            repository.add()
            _items.postValue(repository.getList())
        }
    }

    fun deleteSet() {
        repository.delete()
        _items.postValue(repository.getList())
    }

    fun save() {
        viewModelScope.launch(Dispatchers.IO) {
            repository.save()
        }
    }
}

 

WorkoutListViewModel

class WorkoutListViewModel(application: Application) : AndroidViewModel(application){
    private var _list = MutableLiveData<List<String>>(arrayListOf())

    private val workoutDao = WorkoutListDatabase.getDatabase(application).workoutListDao()
    private val workoutListRepo = WorkoutListRepository(workoutDao)

    val list: LiveData<List<String>>
        get() = _list

    fun getList(part: BodyPart) {
        viewModelScope.launch(Dispatchers.IO) {
            val result = workoutListRepo.getWorkoutList(part)
            _list.postValue(result)
        }
    }
}

 

WorkoutRepository

class WorkoutRepository(private val workoutDao : WorkoutDao, title: String) {
    private val workout = Workout(title = title)
    private var setInfoList = ArrayList<WorkoutSetInfo>()
    lateinit var updatedList : List<WorkoutSetInfo>

    fun changeUnit(unit: WorkoutUnit) {
       updatedList = setInfoList.map { setInfo ->
            setInfo.copy(unit = unit)
        }
    }


    fun add() {
        setInfoList.let { list ->
            val item = WorkoutSetInfo(set = setInfoList.size + 1)
            list.add(item)
            updatedList = setInfoList.toList()
        }
    }

    fun delete() {
        if(setInfoList.size != 0) {
            setInfoList.let { list ->
                list.removeLast()
                updatedList = list.toList()
            }
        }
        return
    }

    fun save() {
        val workoutId = workoutDao.insertWorkout(workout)
        val newWorkoutSetInfoList = setInfoList.map { setInfo ->
            setInfo.copy(parentWorkoutId = workoutId)
        }
        workoutDao.insertSetInfoList(newWorkoutSetInfoList)
    }
    
    fun getList() : List<WorkoutSetInfo> = updatedList
}

 

WorkoutListRepository

class WorkoutListRepository(private val workoutListDao: WorkoutListDao) {
    suspend fun getWorkoutList(part: BodyPart) : List<String> {
        val partList = workoutListDao.getWorkoutList()

        return when(part) {
            is BodyPart.Chest -> partList.chest
            is BodyPart.Back -> partList.back
            is BodyPart.Leg -> partList.leg
            is BodyPart.Shoulder -> partList.shoulder
            is BodyPart.Biceps -> partList.biceps
            is BodyPart.Triceps -> partList.triceps
            is BodyPart.Abs -> partList.abs
        }
    }
}

 

 

codeslave (3,580 포인트) 님이 6월 18일 질문
codeslave님이 6월 19일 수정

1개의 답변

0 추천
Room과 Navigation Component는 서로 관련이 없는데요.  제 눈으로 전체 코드를  돌려보기 전에는 그게 Room과 상관이 있을 거라고 생각하지 않습니다. 삭제했던 HomeFragment에 Room 관련 코드가 들어가 있을 수 있고 말이죠. 만약 정말 그렇다면,  큰 버그죠. 님의 말대로라면, 아마 버그트랙커에 누군가가 이슈를 제기했을 것 같은데요.

그리고 리팩토링을 하실 때는 작은 스텝씩 진행하면서 기존 동작이 잘 되는지 확인해야 해요. 그래서 잘 짜여진 테스트를 확보하는게 리팩토링의 첫번째 단계예요. 참고로 마틴파울러는 리팩토링 후 기존 코드가 동작을 안하게 되는 경우는 리팩토링이 아니라고 했어요. 이게 안되면 님의 경우처럼 구조를 변경하는 리팩토링은 상당히 리스크가 큽니다.
spark (139,480 포인트) 님이 6월 18일 답변
spark님이 6월 18일 수정
그래서 커밋 되돌린후 두번째 진행할때는 코드 혹은 클래스를 지우고나면 하나하나 동작하는지 진행해봤었습니다. 모두 동작했었구요.

사진에서 아래 4개의 destination중 homefragment destination 삭제후 dailyfragment를 추가하기전에 b,c,d 3개의 프래그먼트만 남아있었는데 이 3개도 잘 동작했었습니다.
Room같은것이요.

딱 에러가 났을때가 dailylog fragment를 추가한 후인데..
이걸 좀있다가 새 프로젝트를 만들어서 실험같은걸 해봐야겠어요..
그럼, 간단하게 dummy fragment를 하나 추가한 후  dailyfragment 자리에 끼워넣고, dailyfragment는 삭제해 보세요. 그래도 에러가 난다면 님 말이 맞겠지만, 아니라면 daiylFragment와 관련해서 뭔가 꼬였을 가능성이 있어보이네요.
그렇네요 먼저 선생님 말씀대로 해봐야겠습니다. 감사합니다.

+) 근데 더미 프래그먼트라는게 똑같은 BlankFragment를 말씀하시죠?
선생님 원인을 찾았는데요. Navigation Component랑 전혀 상관없이,
프래그먼트 생성하는게 문제였던것 같습니다.

Project 탭 -> 우클릭 new -> Fragment  -> 원하는 Fragment 클릭후 생성.

이 과정을 거쳐서 프래그먼트를 생성하니 해당 에러가 발생합니다..
그러니까 제가 본문에 언급했던 기존의 HomeFragment를 dailyFragment로 교체했더니 에러가 발생했다 이런건 아무 의미가 없었구요.
그냥 저 방식대로 Fragment를 생성하니 에러가 발생했습니다.

먼저 커밋을 되돌린후 최초로 실행을 해서 앱이 정상적으로 실행되는것을 확인후에 위의 과정을 거쳐서 Fragment를 생성하니 저렇게 됩니다.
에러가 발생한 후에 새로 만든 Fragment 와 레이아웃을 지워도
에러는 안없어지고 계속해서 발생하더라구요.

단 클래스 파일과 레이아웃 파일을 직접 각각 생성하고 Fragment를 상속해서 만드는 방식으로 하니 에러가 안나더라구요. 이건 nav graph를 변경해도 정상적으로 바뀌었습니다..

근데 이게 버그인건지, 아니면 제가 사용하는 Room과 뭐가 충돌이 잇는건지 Room 사용하는 코드를 올려드릴테니 한번 봐주시면 감사하겠습니다ㅠ
제 프로젝트에서만 그런건지는 모르겠습니다. 새프로젝트를 만들어서는 아직 실험을 안해봤네요..
정황상 에러가 다른데서 발생했는데,  그로인해 room이 필요한 클래스를 제대로 생성하지 못하는 걸로 추측이 되는데, 안드로이스튜디오 로그에 관련 에러 메시지가 있을 것 같네요. 어노테이션 프로세서를 사용하는 라이브러리들애서 종종 볼 수 있는 상황이지 않을까 싶네요
소스 공유해 주시면 시간날 때 확인해 볼게요.
어음.. 깃 레파지토리 링크를 public으로 해놓은상태에서 드리면되나요?
Private으로 하고 저를 초대하시면 돼요.
선생님 초대 보냈습니다
해당 리포는 읽기 권한만 있어서 변경사항은 올리지 못했습니다. 님 말처럼 같은 에러가 나왔구요, kapt가 정확하게 상황에 따라 동작하지 않는 문제가 있는 건 맞는듯 한데, 툴의 버그의 일종으로도 볼 수 있을 것 같습니다.
하지만 근본적인 원인은 Room entity클래스를 코트린으로 사용할 경우는 property는 setter를 제공해야 하므로 var를 사용해야 하고,
Dao에서  suspend키워드는 LiveData를 사용할게 아니면 제거하는게 맞구요. 이 두부분을 고치면 빌드가 정상적으로 작동합니다.

그리고 잠정적으로 문제가 될 수 있는 부분은
build.gradle에서 Kotlin설정시 plugin만 설정하시면 되는데, app/build.gradle에 코틀린 라이브러리를 다시 추가하신 것과 plugin버전과 kotlin의 버전이 서로 맞지 않는점,

implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" //app/build.gradle에서 삭제

Junit버전은 명시적으로 특정버전을 사용하세요.
testImplementation 'junit:junit:4.13.2'

그리고 compiledSdk는 31인데 buildTool은 30.0.3으로 버전이 다른 점들은 변경하시는게 좋습니다. compiledSdk, targetSdk와 buildTool은 같은 버전을 사용하시고(targetSdk는 삭제해도 동작을 합니다.)

Firebase 는 버전관리에 문제가 생기지 않도록 bom 을 사용하시는게 좋습니다.(아래 링크참조)
https://firebase.google.com/docs/auth/android/start#kotlin+ktx

그리고 Hilt는 추가만 하시고 실제적으로는 사용하지 않은 것 처럼 보이네요. 사용하지 않으면 삭제하세요.
아 감사합니다. 말씀대로 Workout Entity의 val -> var로 변경, 그리고 suspend를 제공해주니 프로젝트 탭에서 프래그먼트를 생성해줘도 에러가 안나네요..
다른 Entity 클래스는 val로 존재하는 것도 있는데 왜그런지는 모르겠습니다..
아무튼 Kotlin Gradle 관련은
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" //app/build.gradle에서 삭제 << 이것만 진행해주면 되나요?


그리고 혹시 읽기권한인가 그거 해제해드려야하나요?

지금 프로젝트가 커밋을 되돌린상태라 지울게 안지워져있고 수정해야할게 안되어있고 그런상태거든요
...