안드로이드 스튜디오 메모 앱 만들기(7)

윤재환·2024년 11월 23일
post-thumbnail

제목추가 하기전에 오류 잡기

우선 제목을 추가하기 전에 이미 작성해둔 메모들은 제목이 없습니다
지금의 데이터 형식은

["메모 내용 1", "메모 내용 2"]

이런식으로 되어 있지만

제목을 추가하게되면 새로운 데이터 구조는 다음과 같을 것 입니다.

[{"title": "제목1", "content": "메모 내용1"}, {"title": "제목2", "content": "메모 내용2"}

위에 데이터 방식과 매우 다른 방식이라 형식 불일치로 인해 오류가 발생할 여지가 있습니다.

이러한 오류를 역직렬화 오류라고 부릅니다.

기존에 저장된 데이터를 가져올떄 데이터 형식이 달라서 데이터를 읽지 못하고 예외를 발생시킬 수 있습니다.

그럼 어떻게 잡을까?

해당 경우는 프로젝트를 하면서 많이 경험을 했습니다.

프로젝트를 하면서 데이터베이스를 고칠 일이 많던 저는 이러한 문제를 "기본값"을 만들어 null이 생기지 않게 하는 것 입니다.

그럼 이제 코드를 작성해 보겠습니다.


코드 작성

위에서 말했듯이 우선 데이터 구조를 바꿔야합니다.
기존에 제목이 없는 메모들도 있으니 제목에는 기본값을 꼭 넣어줍시다.
MainActivity.kt에서 데이터 클래스를 정의해 줍시다.

data class Memo(
	val title: String = " 제목 없음", // 기본값
    val content: String // 메모 내용
)
private val memoList = mutableListOf<Memo>() // 기존에 String대신에 Memo객체를 저장

이후 activity_main.xml에서 제목을 추가해 줍니다.

<EditText
        android:id="@+id/editTextTitle"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="제목을 입력하세요"/>

id는 editTextTitle로 설정해 주었습니다.


이젠 item_memo.xml 수정 하겠습니다.

    <TextView
        android:id="@+id/textViewTitle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="18sp"
        android:textStyle="bold"/>
    

이제 Memo객체를 처리하도록 어댑터를 변경합니다.

MemoAdapter.kt로 찾아갑시다.


class MemoAdapter(
    // MutableList의 데이터 클래스를 MainActivity안에 있는 Memo로 변경
    private val memoList: MutableList<MainActivity.Memo>,
    private val onDelete: (Int) -> Unit       //삭제 콜백 추가
) : RecyclerView.Adapter<MemoAdapter.MemoViewHolder>() {



    // ViewHolder 클래스 정의
    class MemoViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        val textViewTitle: TextView = itemView.findViewById(R.id.textViewTitle)
        val textViewContent: TextView = itemView.findViewById(R.id.textViewMemo)
        //제목이 추가되어 데이터 구조가 변경되어 아래 코드 수정
       // val textViewMemo: TextView = itemView.findViewById(R.id.textViewMemo)
        val buttonDeleteMemo: Button = itemView.findViewById(R.id.buttonDeleteMemo)
    }


    // onBindViewHolder: ViewHolder에 데이터를 바인딩
    override fun onBindViewHolder(holder: MemoViewHolder, position: Int) {
        //데이터 구조 변경후 아래 코드 수정
        //holder.textViewMemo.text = memoList[position]
        val memo = memoList[position]
        holder.textViewTitle.text = memo.title
        holder.textViewContent.text = memo.content

        //삭제 버튼 클릭 이벤트 처리
        holder.buttonDeleteMemo.setOnClickListener{
            onDelete(position) // 삭제 콜백 호출
        }
    }

   
}

변경된 부분만 작성 했습니다.
기존에는 textViewMemo 하나에 데이터를 바인딩했지만, 제목(textViewTitle)과 내용(textViewContent)을 각각 바인딩하도록 변경했습니다.


이제 다시 MainActivity.kt를 작성하겠습니다.

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // EditText, Button, RecyclerView 참조
        val editTextTitle = findViewById<EditText>(R.id.editTextTitle)
        val editTextMemo = findViewById<EditText>(R.id.editTextMemo)
        val buttonAddMemo = findViewById<Button>(R.id.buttonAddMemo)
        val recyclerViewMemos = findViewById<RecyclerView>(R.id.recyclerViewMemos)

        // RecyclerView 설정
        recyclerViewMemos.layoutManager = LinearLayoutManager(this)
        adapter = MemoAdapter(memoList) { position ->
            deleteMemo(position) // 삭제 처리
        }
        recyclerViewMemos.adapter = adapter

        // 저장된 메모 불러오기
        loadMemos()

        // 버튼 클릭 이벤트
        buttonAddMemo.setOnClickListener {
            //제목 추가로 아래 코드 수정
            //val memoText = editTextMemo.text.toString()
            val titleText = editTextTitle.text.toString()
            val contentText = editTextMemo.text.toString()

            if (contentText.isNotEmpty()) {
                //제목 추가로 아래 코드 수정
                //memoList.add(memoText) // 메모 리스트에 추가
                val newMemo = Memo(
                    title = if (titleText.isNotEmpty()) titleText else "제목 없음",
                    content = contentText
                )
                memoList.add(newMemo)
                adapter.notifyDataSetChanged() // 어댑터에 변경 사항 반영
                editTextTitle.text.clear()
                editTextMemo.text.clear()
                saveMemos() // 메모 저장
            }
        }
    }

우선 제목을 추가해서 val editTextTile를 추가 했습니다.
이후 MemoAdapter.kt와 같이 memoText하나 대신에 제목, 내용으로 바꿔주었습니다.


에러

이렇게 했는데도 역직렬화 오류가 나왔다...
에러 코드는 이렇습니다.

Expected BEGIN_OBJECT but was STRING at line 1 column 3 path $[0]
  • 해당 오류는 Gson이 Json데이터를 Memo객체로 역직렬화 하려 할 때, 데이터 구조가 예상과 다르다는 것을 나타냅니다.
  • 저장된 Json데이터는 단순 문자열 리스트(List<String>)이고, 현재 앱은 MutableList<Memo>를 기대하고 있습니다.

문제 발생 코드

해당 에러는 MainActivity.ktloadMemos 메서드에서 발생했습니다.

과거 코드

val type = object : TypeToken<MutableList<String>>() {}.type
val loadedMemos: MutableList<String> = gson.fromJson(json, type)
memoList.addAll(loadedMemos)

현재 코드

val type = object : TypeToken<MutableList<Memo>>() {}.type
            val loadedMemos: MutableList<Memo> = gson.fromJson(json, type)
            memoList.addAll(loadedMemos)
  • 현재 Json데이터는 String리스트 형식으로 저장되어 있지만, 앱의 새로운 구조는 Memo데이터 클래스(title, content)를 포함하고 있습니다.
  • Gson은 MutableList<Memo>로 데이터를 역직렬화하려 했지만, Json 데이터가 String형식이기 때문에 구조가 맞지 않아 에러가 발생했습니다.

기존 JPA와 같은 데이터베이스에서는 새로운 열(column)을 추가할 때 기본값을 설정하여 데이터 구조 변경 문제를 해결할 수 있었습니다. 하지만, 이 앱은 Json으로 데이터를 저장하고 있기 때문에 Json 데이터 구조가 변경되지 않아 역직렬화 과정에서 문제가 발생한 것으로 보입니다.


해결 방법

이러한 문제를 해결을 하기 위해서는 기존 Json코드를 새로운 데이터 구조로 변환하는 과정을 만들어야 합니다.

MainActivity.ktloadMemos를 수정해 봅시다.

if (json != null) {
            try {
                val type = object : TypeToken<MutableList<Memo>>() {}.type
                val loadeMemos: MutableList<Memo> = gson.fromJson(json, type)
                memoList.addAll(loadeMemos)
            }catch (e: Exception){
                val oldType = object : TypeToken<MutableList<String>>() {}.type
                val oldMemos: MutableList<String> = gson.fromJson(json, oldType)

                val convertedMemos = oldMemos.map { Memo(title = "제목 없음", content = it) }
                memoList.addAll(convertedMemos)

                saveMemos()
            }
            adapter.notifyDataSetChanged()
        }

기존 코드를 수정하여, 먼저 Memo 데이터 형식으로 저장된 데이터를 불러오도록 하고, 만약 데이터를 불러오는 데 실패할 경우 oldType으로 저장된 데이터(이전 형식)를 처리하도록 변경하였습니다. 이후, 이전 형식의 데이터를 Memo 데이터 형식으로 변환하여 기존 구조에 맞게 저장하는 로직을 추가했습니다.


잘 불러와지기는 하는데 "제목내용"
이런식으로 합쳐저있어서 가독성이 많이 떨어졌습니다.
다음 포스트에서는 제목과 내용을 좀더 가독성 있게 변경하겠습니다.

profile
백엔드 개발에 관심있는 1인

0개의 댓글