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

arraystr이 초기화 되지 않았다고 계속 뜨면서, 앱이 종료됩니다.

0 추천
class SearchActivity : AppCompatActivity() {

    private lateinit var adapter: Rc_Adapter

    private lateinit var location_name: String
    private lateinit var rp1: String
    private lateinit var rp2: String
    private lateinit var rp3: String

    private lateinit var am: AssetManager       //경로: 에셋의 경로는 일반 경로와 다르게 선언한다.
    private var bufrd: BufferedReader? = null
    private lateinit var inst: InputStream      //파일 리더 선언: 파일을 오픈하여, 파일클래스 얻어, 파일 클래스의 함수들을 이용하여 파일을 다루는 것이 목적이다. //asset에 저장된 파일들은 InputStream으로만 오픈 가능하다.
    private lateinit var tx: String
    private lateinit var text: String           //문자열로된 파일의 문자열을 읽어와서 저장하기 위해 선언해주는 프로퍼티다. 이 저장된 값은, 텍스트 뷰 객체의 텍스트 속성값에 대입된다.

    private lateinit var arraystr: ArrayList<VO>
    private lateinit var arraystr2: ArrayList<String>

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

        am = resources.assets       //경로: 에셋의 경로는 일반 경로와 다르게 선언한다.


        search_btn.setOnClickListener(object : View.OnClickListener {
            override fun onClick(v: View?) {
                for (i in 1..2) {
                    when (i) {
                        1 -> {
                            rp1 = "구약"
                            for (o in 1..39) {
                                when (o) {
                                    1 -> {
                                        rp2 = "1. 창세기"
                                        for (p in 1..50) {
                                            when (p) {
                                                p -> {
                                                    rp3 = "genesis$p"
                                                    memem("창세기", p)
                                                }
                                            }
                                        }
                                    }
.............중략

fun memem (book: String, chapter: Int) {
        location_name = "$rp1/$rp2/$rp3.txt"
        try {
            inst = am.open(location_name)

            //★★★권/장/절을 선택할 때에, when문으로 처리하자. 리사이클러뷰의 버튼 중 어느 인덱스에 해당하는 버튼을 누르는지에 따라, 해당 "장"을 오픈해주자. 그러므로 인덱스값을 when문의 매개변수에 인수로써 보내어 대입해주자.
            //inst = am.open("구약/1. 창세기/genesis1.txt")    //여기가 시작지점: 파일을 여는 과정에서 리턴되는 파일 핸들(Handle) 확보, 오픈할 파일이 없으면, catch문을 실행하여 예외상황에 대비한다.
            bufrd = BufferedReader(InputStreamReader(inst))
            //val textView = view.findViewById<View>(R.id.txtRead) as TextView        // 텍스트 뷰 객체를 생성한다.
            //리사이클러뷰에 절에 대한 데이터를 대입해주고, 장 절에 대한 리사이클러뷰 목록을 만들어주고, 클릭 이벤트를 구현해준 후, 각 버튼의 포지션 값을 받아, 이 값을 오픈의 when문의 매개변수에 인수로 보내주자.
            for(i in 1..200) {
                when(i) {
                    i -> {
                        if ((bufrd!!.readLine().also { tx = it }) != null) {
                            text = tx.toString()  //바이너리를 바이트 형식의 문자열로 바꿔서 텍스트 뷰 객체에 대입해준다.
                            arraystr2.add(text)
                            //ry_desc.text = text        //텍스트 뷰에 읽어들인 텍스트들을 대입한다.
                            //addItem(i.toString(), text)     //이거 괄호 안에서 옆으로 추가해야지, 밑에다가 새로 addItem하면 오류나...
                            //store = text        //여기로 포지션 값을 가져오자.

                            //버퍼리드로 읽어온 한 줄의 텍스트 중에서, 원하는 문자열을 찾아주는 코드다.
                            if (arraystr2.contains(editT1.text.toString()) == true) {
                                addItem("[${book}: ${chapter}장${i}절]", text)
                            }
                            arraystr2.clear()
                        }
                    }
                }
            }

        } catch (e: Exception) {
            e.printStackTrace()
        }

        try {
            bufrd?.close()
            inst.close()
        } catch (e: Exception) {
            e.printStackTrace()
        }

        adapter = Rc_Adapter(arraystr)   <-★★바로 여기가 833번줄 에러가 나는 곳입니다.
        rv_search.setAdapter(adapter)

        adapter.notifyDataSetChanged()
    }

    //리사이클러뷰 어댑터에 implement Filterable
    class Rc_Adapter (var mem: ArrayList<VO>) : RecyclerView.Adapter<Rc_Adapter.ViewHolder>() {


        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
            val v: View = LayoutInflater.from(parent.context).inflate(R.layout.activity_search_item, parent, false)
            return ViewHolder(v)
        }
        override fun onBindViewHolder(holder: ViewHolder, position: Int) {
            val memi = mem[position]
            holder.itemView.search_item1.setText(memi.getTitle())
            holder.itemView.search_item2.setText(memi.getDesc())
        }

        override fun getItemCount(): Int {
            return mem.size
        }

        inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {

        }
    }

    fun addItem (bcv: String, contents: String) {
        val dataC = VO()
        dataC.setTitle(bcv)
        dataC.setDesc(contents)
        arraystr.add(dataC)
    }
}

class VO {

    //private var icon: Drawable? = null
    private var title: String? = null
    private var desc: String? = null
    //그러면 변수를 선언할 때에도, 얼마나 많은 변수가 필요해질지 모르겠다... 데이터 나열 형태의 변수는 없을까? 데이터를 대입해주면, 스스로 판단하고, 변수를 새로 생성하는 것이다.
    //코딩 아이디어: 겟과 셋을 하나만 선언해보자. 그리고, 그 내부에, 조건문을 배치해서, icon이라 명시하지 말고, 미지수로 두자. 어느 형태의 데이터든지 받아서 셋하고, 겟 할 수 있는 형식으로 만들어야 한다.
    /*fun getIcon(): Drawable? {
        return icon
    }

    fun setIcon(icon: Drawable?) {
        this.icon = icon
    }*/

    fun getTitle(): String? {
        return title
    }

    fun setTitle(title: String?) {
        this.title = title
    }

    fun getDesc(): String? {
        return desc
    }

    fun setDesc(desc: String?) {
        this.desc = desc
    }
}
    kotlin.UninitializedPropertyAccessException: lateinit property arraystr has not been initialized
        at com.example.yhw.SearchActivity.memem(SearchActivity.kt:833)<-- ★이거랑
        at com.example.yhw.SearchActivity$onCreate$1.onClick(SearchActivity.kt:56)<-- ★이것입니다.
        at android.view.View.performClick(View.java:7352)
        at android.widget.TextView.performClick(TextView.java:14230)
        at android.view.View.performClickInternal(View.java:7318)
        at android.view.View.access$3200(View.java:846)
        at android.view.View$PerformClick.run(View.java:27800)
        at android.os.Handler.handleCallback(Handler.java:873)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:214)
        at android.app.ActivityThread.main(ActivityThread.java:7050)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:494)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:965)

 

제가 만드려고 하는 기능은, 검색 기능인데요. 사용자가 특정 단어가 포함된 구절을 찾고자 할 때에,

사용자가 입력한 단어를 파일리더로 읽어온 텍스트 한 줄 속에서 contains()함수로 찾아내어,

특정 단어가 포함된 구절을 배열 변수에 담아놓고, 이 배열 변수의 데이터들을 리사이클러뷰의 어댑터

클래스로 보내어, 화면에 리스트 형식으로 출력시켜주려고 했습니다만,

arraystr이라는 배열 변수가 초기화가 되지 않았다고 뜨는 것으로보아,

아무래도 contains()함수를 제가 사용할 줄 모르는게 아닌가 싶습니다.

하지만, 책에서 가르쳐주기를, 배열 변수에 담긴 데이터 중에, 원하는 문자열을 갖은 데이터를 찾으면,

true를 반환해준다기에 저렇게 코딩한 것인데.. 안되네요...

그리고 for문이랑 when문 섞어가면서, 원하는 기능이 수행되도록 코딩해놓긴 했는데...

이 조건문 보다 더 좋은 방식이 아무래도 있을것 같다는 느낌이 듭니다. 조언좀 부탁드립니다. ^^;

상쾌한 (1,290 포인트) 님이 3월 7일 질문
상쾌한님이 3월 7일 수정

2개의 답변

+1 추천
 
채택된 답변

코틀린은 사용하시는데 자바처럼 사용하고 계시네요. 코틀린에는 코틀린에 더 적합한 사용법이 있습니다.

private lateinit var arraystr: ArrayList<VO>
private lateinit var arraystr2: ArrayList<String>

먼저, 에러 메세지는 위의 변수들이 lateinit 으로 지정되어어 초기화가 안된 상태에서 해당 변수를 사용하려고 접근했기 때문입니다. 간단한 해결방법은 초기화를 해주는 겁니다.

private var arraystr = arrayListOf<VO>()
private var arraystr2 = arrayListOf<String>()

그리고 var과 ArrayList를 둘다 사용하실 필요가 없습니다.  둘 중의 하나만 있으면 됩니다.

private val arraystr = arrayListOf<VO>()
private val arraystr2 = arrayListOf<String>()

or

private var arraystr = listOf<VO>()
private var arraystr2 = listOf<String>()

저는 개인적으로 첫번째 형태를 좀 더 선호합니다.

그리고 버튼 이벤트 리스너 같은 경우는 SAM 을 이용해서 람다 표현식만 넘기시면 됩니다. 아래가 가독성이 훨씬 좋죠.

search_btn.setOnClickListener { view: View -> 
    
}

그리고 코틀린에서는 파일을 읽어오는데 자바 IO클래스를 그대로 사용하실 필요가 없습니다.

val content = input.bufferedReader().use { it.readText() } 

단순 데이터를 담는 클래스는 data class를 사용하세요.

data class VO(
   val icon: Drawable? = null
   val title: String,
   val desc: String
)


val dataC = VO(title = bcv, desc = contents)


검색버튼 클릭이벤트 안의 로직은 이해하기 쉽도록 리팩토링이 꼭 필요해 보이네요.

spark (42,080 포인트) 님이 3월 7일 답변
상쾌한님이 3월 7일 채택됨
ㅎㅎ 그렇게 말씀하실것 같더라고요. ^^ 아직 코틀린을 다 배우지 못했습니다. 답변해주셔서 감사합니다. ^^
+1 추천

Assets에 있는 파일이 어떤 구조로 되어 있는지 모르겠지만, 이걸 읽어서 필요한 데이터 구조로 변환해서 사용하세요. 님의 경우는 VO 클래스로 먼저 변환하세요.

data class Book(
    val icon: Drawable? = null,
    val title: String,
    val desc: String
)

private val books: List<Book> = listOf()

override fun onCreate(saveInstanceState: Bundle?) {
      super.onCreate(saveInstanceState)        
      //생략
      
      lifecycleScope.launch(Dispatcher.IO) {
          books = loadDataSource()
      }

     search_btn.setOnClickListener {view: View -> 
           searchBook();
      }
}

// Assets에서 파일을 읽어와서 List<Book> 으로 변환
suspend fun loadDataSource(): List<Book>() {
      
}

private fun searchBook() {
   lifecycleScope.launch {
        showLoadingProgress();
        val searchedBooks: List<Book> = queryBook(editT1.text.toString())
        updateBookList(searchedBooks)
        hideLoadingProgress();
   }
}

private suspend fun queryBook(searchQuery: String): List<Book> {
     books.filter { book ->
         book.contains(searchQuery)
     }
}

private fun updateBookList(books: List<Book>) {
     //RecyclerViewAdapter 업데이트
}

private fun showLoadingProgress() {
   // Progressbar 보여주기
}

private fun hideLoadingProgress() {
  // Progressbar 감추기
}

파일을 읽어오는 부분은 IO관련 작업이기 때문에 반드시 백그라운드 쓰레드에서 하시길 권장드립니다. 서치 작업도 비동기형태로 처리를 하시구요.

그리고 검색결과가 없을 시 검색결과가 없다는 걸 보여주시는 게 User experience상 더 좋을 것 같습니다.

spark (42,080 포인트) 님이 3월 7일 답변
spark님이 3월 7일 수정
알려주신것은 자바io 빼고는 다 바꿨습니다. 이것은... 다 처음보는 함수들이라.. 복사 붙여넣기해서 한 번 봐야 알것 같습니다. 감사합니다. ^^
와 성공했습니다. ^^ 도와주셔서 감사합니다. ^^ 쓰레드랑 비동기는 배우고 있는 중입니다. 멀티 쓰레드에 대해서 읽고 있는 중인데.. 어렵지 않은 내용이네요. 계속 미뤄 왔었는데, 어렵지 않아서 다행입니다. 기능은 완성했는데, 검색 버튼을 누르면, 화면이 잠깐 멈추더라고요. 검색하는 동안에, 로딩 프로그레스바를 켜려고 했는데... 화면이 멈추는것 때문에 프로그레스바가 출력이 안되더라고요. 그래서 쓰레드 때문인가 싶어서 공부하고 있는 중입니다. ^^
위의 예제는 아주 간단한 작업이기 때문에 Coroutines를 사용해도 전혀 문제가 없지만  Coroutines는 생각보다 상당히 복잡합니다. 특히 작업취소나 예외처리를 해야 한다면, Coroutines가 어떻게 동작하는지 제대로 이해하지 않으면 이상한 버그를 쉽게 만날 수 있습니다. 따라서 Coroutines Scope 에 한개 이상의 Coroutines를 실행하거나 Coroutines안에 다른 Coroutines 를 실행하는 경우가 있다면, 해당 부분을 공부하셔야 할 겁니다.
그리고 화면이 멈춘다는 것은 코드 어딘가에 UI thread를 blocking하고 있을 가능성이 크다는 것을 의미합니다. 따라서 background에서 실행해야 할 부분과 ui thread에서 실행해야 할 부분을 잘 분리하셔야 합니다. ui thread에서 실행해야 할 부분이란 화면에 무언가를 업데이트하는 동작이겠죠. 제가 님의 코드를 사용하지 않기 때문에 어디가 정확하게 그렇다고는 말씀드릴 수가 없네요.
혹시... spark님 do it! 코틀린 프로그래밍 저자 아니신가요? 설명하시는 형태나, 가르쳐주시는 내용이 흡사하던데요... ㅎㅎ
아니예요. 그냥 뒤쳐지지 않으려고 공부를 꾸준히 하는 편이긴 합니다만.
오늘 저녁에 적어주신걸로 성능 개선 해봤는데, 와~ 진짜 엄청 빨라지더라구요. 2초나 걸리던 검색 시간이 0.1초로 바뀌었어요.... 이걸로 다른 부분도 성능 개선 해줄 수 있을 것 같아요. 감사합니다. ^^
...