본문 바로가기
Kotlin

Kotlin - Realm 사용하기

by 봄석 2019. 1. 7.

Kotlin - Realm 사용하기

Realm은 적은 코드로 데이터베이스를 작성할 수 있습니다. 

SQLite는 SQL 문법을 어느정도 알고있어야 하는 반번 Realm은 SQL 문법을 전혀 몰라도 사용할 수 있습니다. 



Realm 데이터베이스 사용 준비

Realm 을 사용하려면 먼저 프로젝트수준의 build.gradle 파일을 열고 

dependencies 항목에 아래와같이 플러그인을 추가합니다.

dependencies {
           ....
        classpath 'io.realm:realm-gradle-plugin:5.2.0'
     
    }



다음으로 모듈 수준의 build.gradle 파일을 열고 상단에 아래와 같은 두 가지 플러그인을 추가합니다.

apply plugin: 'realm-andorid'
 

apply plugin: 'kotlin-kapt'



Realm 객체로 만드는 방법

Realm 사용방법을 간단히 알아보겠습니다.

Dog라는 클래스로 표현해보겠습니다. 이와 같은 모양의 클래스를 모델클래스라 합니다.

class Dog(val id: Long,
        val name:String ="",
        var age: Int=0) {

}



Realm 에서 테이블로 사용하려면 모델 클래스 앞에 open을 붙히고 RealmObject 클래스를 상속받으면 됩니다.

open class Dog(val id: Long,
        val name:String ="",
        var age: Int=0) : RealObejct() {
}




그럼 할 일 리스트라는 주제로  Realm 모델클래스를 작성해 보겠습니다.


Realm 모델 클래스 작성

먼저 Realm에서 위와 같은 테이블 정보를 다룰 Todo 모델 클래스를 새로 작성합니다.

프로젝트 창에서 마우스 우클릭 또는 안드로이드 스튜디오 상단 메뉴에서

[File]->[New]->[Kotlin File/Class]를 클릭하고 Todo 라는 이름으로 새로운 클래스를 생성합니다.

open class Todo(
    @PrimaryKey var id: Long = 0,
    var title: String = "",
    var date: Long = 0
) : RealmObject() {
}



Realm 초기화

앱이 실행될 때 제일 먼저 Realm 을 초기화해서 다른 액티비티가 사용하도록 할 수 있습니다.

그러려면 앱을 실행하면 가장 먼저 실행되는 애플리케이션 객체를 상속하여 Realm을 초기화 해야합니다. 


프로젝트 창에서 마우스 우클릭 또는 안드로이드 스튜디오 상단 메뉴에서

[File] -> [New] -> [Kotlin File/Class]를 클릭하고 MyApplication이라는 이름으로 클래스를 생성합니다. 그리고 아래와 같이 수정해 줍니다.

class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        Realm.init(this)
    }
}



1) Application 클래스를 상속받게 MyApplication을 선언해 줍니다.

2) onCreate() 메소드를 오버라이드 합니다 ( onCreate 메소드는 액티비티가 생성되기 전에 호출됩니다)

3) Realm.init() 메소드를 사용하여 초기화 합니다.

4) manifast파일에서 <application> 태그안에  android:name=".MyApplication" 을 추가해줍니다.


액티비티에서 Realm 인스턴스 얻기

val realm= Realm.getDefaultInstance() //인스턴스 얻기
            
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_edit)
    }
 
    override fun onDestroy() {
        super.onDestroy()
        realm.close() //인스턴스해제
    }




Realm 에 데이터추가 (할 일 추가하기) fun insertTodo(){}

class EditActivity : AppCompatActivity() {
 
    val realm= Realm.getDefaultInstance() //인스턴스 얻기
    val calendar:Calendar= Calendar.getInstance() //날짜를 다룰 캘린더 객체얻기
 
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_edit)
    }
 
    private fun insertTodo(){
        realm.beginTransaction() //트랜잭션 시작
 
        val newItem =realm.createObject<Todo>(nextId()) //새객체생성<Todo>타입
        //createObject<T : RealmModel> (primaryKeyValue : Any?)  --프라이머리키를 지정
 
        //값설정
        newItem.title=todoEditText.text.toString()
        newItem.date =calendar.timeInMillis
 
        realm.commitTransaction() //트랜잭션반영
 
        //다이얼로그 표시
        alert("내용이 추가되었습니다"){
            yesButton { finish() }
        }.show()
    }
 
    //다음 id를 반환
    private fun  nextId():Int{//Realm 은 기본키 자동 증가 기능을 제공하지않아 가장 큰 id값을 얻고 1을 더한 값을 반환하는 메소드
        val maxId=realm.where<Todo>().max("id")// where 테이블 모든 값 얻기, 이메소드는 RealmQuery 객체를 반환, max는 가장 큰 값 얻음
        if(maxId!=null){
            return maxId.toInt()+1
 
        }
        return 0
    }
    override fun onDestroy() {
        super.onDestroy()
        realm.close() //인스턴스해제
    }
}



Realm.getDefaultInstance()로 객체를 얻은후 사용후에 마지막으로 

onDestroy() d에서 인스턴스를 해제합니다.


realm.beginTransaction() 으로 트랜잭션을 시작후 작업완료후 

realm.commitTransaction() 으로 닫아주어야 합니다.


Realm은 기본키 자동기능을 추제공하지않아 fun nextId() 메소드를 작성했습니다.

realm.where<RealmModel>()로 모든 값을 얻어와 max(filedName)으로 필드의 가장 큰 값을 가져옵니다. 그리고 1증가시켜줍니다.


Realm 데이터수정 (할 일 수정하기) fun updateTodo() {}

 private fun updateTodo(id:Long){ //id를 인자로 받습니다.
        realm.beginTransaction() //트랜잭션 시작
        
        val updateItem=realm.where<Todo>().equalTo("id",id).findFirst()!! // Todo모델 클래스로부터 데이터를 얻습니다.
        //equlTo() 메소드로 조건을 설정합니다. "id" 컬럼의 값이 매개변수로 받은 id값이 있다면 findFirst() 메소드로 첫 번째 데이터를 반환합니다.
        //값 수정
        updateItem.title=todoEditText.text.toString()
        updateItem.date=calendar.timeInMillis
        
        realm.commitTransaction() //트랜잭션 종료 반영
        
        //다이얼로그 표시
        
        alert("내용이 변경되었습니다"){
            yesButton { finish() }
        }.show()
    }



updateTodo() 메소드는 id 를 매개변수로 받습니다.

트랜잭션을 시작하고, where<T>()로 T타입의 객체로부터 모든 데이터를 얻습니다. 

equalTo() 메소드로 "id" 컬럼값에 매개변수로 받은 id값이 있다면 findFirst() 메소드로 첫 번째 데이터를 반환합니다.



Realm 데이터삭제 (할 일 삭제하기) fun deleteTodo(){}

 private fun deleteTodo(id:Long){
        realm.beginTransaction()
        val deleteItem =realm.where<Todo>().equalTo("id",id).findFirst()!!
        //삭제할 객체
        deleteItem.deleteFromRealm() //삭제
        realm.commitTransaction()
        
        alert("내용이 삭제되었습니다"){
            yesButton { finish() }
        }.show()
    }



deleteTodo 메소드는 id를 매개변수로 받습니다.

트랜잭션을 시작하고. where<T>() 로 T타입 객체로부터 모든 데이터를 얻어 

equalTo() 메소드로 "id" 컬럼값에 매개변수로 받은 id값이 있다면 findFirst() 메소드로 첫 번째 데이터를 반환합니다.

deleteFromRealm() 메소드를 이용해 데이터를 삭제하고 commitTransaction() 호출합니다.



추가/수정 분기처리 하기

class EditActivity : AppCompatActivity() {
 
    val realm= Realm.getDefaultInstance() //인스턴스 얻기
    val calendar:Calendar= Calendar.getInstance() //날짜를 다룰 캘린더 객체얻기
 
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_edit)
 
        //업데이트 조건
        val id=intent.getLongExtra("id",-1L) //첫화면에서 인텐트로 넘어오는 Long을 받음
        if(id== -1L){
            insertTodo()
        }else{
            updateTodo(id)
        }
 
        //캘린터 뷰의 날짜를 선택했을 때 Calendar 객체에 설정
        calendarView.setOnDateChangeListener { view, year, month, dayOfMonth ->
            calendar.set(Calendar.YEAR,year)
            calendar.set(Calendar.MONTH,month)
            calendar.set(Calendar.DAY_OF_MONTH,dayOfMonth)
        }
    }
 
    //추가 모드 초기화
    private fun insertMode(){
        //삭제 버튼을 감추기
        deleteFab.visibility=View.GONE
        //완료 버튼을 클릭하면 추가하기
        doneFab.setOnClickListener {
            insertTodo()
        }
    }
 
    //수정모드 초기화
    private fun updateMode(id:Long){
        //id에 해당하는 객체를 화면에 표시
        val todo=realm.where<Todo>().equalTo("id",id).findFirst()!!
        todoEditText.setText(todo.title)
        calendarView.date=todo.date
 
        //완료 버튼을 클릭하면 수정
        doneFab.setOnClickListener {
            updateTodo(id)
        }
        //삭제버튼을 누르면 삭제
        deleteFab.setOnClickListener {
            deleteTodo(id)
        }
    }
 
......
}




리스트뷰와 데이터베이스 연동

class MainActivity : AppCompatActivity() {
 
    val realm= Realm.getDefaultInstance() // Realm 객체 초기화
 
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        setSupportActionBar(toolbar)
 
        addFab.setOnClickListener { view ->
           startActivity<EditActivity>()
        }
 
        //전체 할일 정보를 가져와서 날짜 순으로 내림 차순 정렬
        val realmResult=realm.where<Todo>()
            .findAll()
            .sort("date", Sort.DESCENDING) //Sort.DESCENDING - 내림차순 , Sort.ASCENDING - 오름차순
        
    }
 
       
    ....
 
    override fun onDestroy() {
        super.onDestroy()
        realm.close()  //Realm 객체 리소스 해제
    }
}



맨위 전역변수에 Realm 객체 초기화함. 사용후에는 onDestroy() 메소드에서 리소스 해제.

sort(fieldName: String, sortOrder) 메소드로 오름차순, 내림차순으로 정렬할 수 있다.



어뎁터 작성하기

일반적으로 리스트 뷰용 어댑터는 BaseAdapter 클래스를 상속받아 사용하지만 

Realm을 사용할 때는 Realm에서 제공하는 RealmBaseAdapter 클래스를 상속 받습니다.

RealmBaseAdapter를 사용하려면 우선 모듈 수준의 build.gradle 파일에 라이브러리 의존성을 추가합니다.

dependencies {
    implementation 'io.realm:android-adapters:2.1.1'
    ...

}


프로젝트 창의 패키지명에서 마우스 우클릭 또는 안드로이드 스튜디오 상단 메뉴에서 

[File]->[New]->[Kotlin File/Class]를 클릭하고 TdoListAdapter를 생성합니다.

class TodoListAdapter(realmResult:OrderedRealmCollection<Todo>):RealmBaseAdapter<Todo>(realmResult){
   
    //아이템에 표시하는 뷰를 구성합니다.
    override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
        //position ->리스트뷰의 아이템 위치입니다, convertView -> 재활용 되는 아이템의 뷰입니다 , parent ->부모 뷰 즉 여기서는 리스트 뷰의 참조를 가리킵니다.
    }
}




OrderedRealmCollection<Todo>타입의 realmResult를 생성자로 받고 

RealmBaseAdapter<Todo>를 상속하는 클래스이며,


getView() 메소드를 구현해 주어야 합니다.



리스트 뷰의 뷰 홀더 패턴

리스트뷰에서는 리스트를 표시할 때 성능을 향상시킬 목적으로 일반적으로 뷰 홀더 패턴을 적용합니다. getView() 메소드가 아이템이 화면에 표시될 때마다 호출되므로 최대한 효율적인 코드를 작성해야하기 때문입니다. 뷰 홀더 패턴은 한번 작성한 레이아웃을 재사용 하고 내용만 바꾸는 방법입니다.


아이템이 최대 6개의 아이템이 표시된다고 했을때 7번 아이템 밑으로 스크롤하면 다시 1,2번 아이템이 재활용하여 보여주게 됩니다.


뷰 홀더 패턴을 적용해 어댑터 코드 작성

class TodoListAdapter(realmResult:OrderedRealmCollection<Todo>):RealmBaseAdapter<Todo>(realmResult){
 
    //아이템에 표시하는 뷰를 구성합니다.
    override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {  //매 아이템이 화면에 보일 때 마다 호출
        //position ->리스트뷰의 아이템 위치입니다, convertView -> 재활용 되는 아이템의 뷰입니다 , parent ->부모 뷰 즉 여기서는 리스트 뷰의 참조를 가리킵니다.
 
        val vh: ViewHolder
        val view:View
 
        if(convertView==null){ //null 일때 레이아웃을 작성
            view=LayoutInflater.from(parent?.context)
                .inflate(R.layout.item_todo,parent,false)// Xml 레이아웃 파일을 읽어서 뷰로 반환합니다.
                    //inflate(resource :Int, root:ViewGroop, attachToRoot:Boolean)
                    // resource-> 불러올 xml 리소스 id, root-> 불러온 파일이 붙을 뷰그룹인 parent를 지정,  attachToRoot xml 파일을 불러올때는 false 지정
 
            vh= ViewHolder(view) //뷰홀더 객체 초기화
            view.tag=vh          //뷰홀더 객체는 tag 프로퍼티로 view에 저장됨. tag 프로퍼티는 Any 형으로 어떠한 객체도 지정가능합니다.
        }else{  //convertView가 null이 아니라면 이전에 작성했던 convertView를 재사용
            view=convertView
            vh=view.tag as ViewHolder  //tag프로퍼티에 저장된 Any형의 객체를 ViewHolder 형으로 형변환
        }
 
        if(adapterData!=null){                      //RealmBaseAdapter의 adapterData 프로퍼티로 realm의 데이터에 접근할 수 있습니다.
            val item=adapterData!![position]        //해당 위치(position)의 데이터를 item에 담습니다.
            vh.textTextView.text =item.title
            vh.dateTextView.text= android.text.format.DateFormat.format("yyyy/MM/dd",item.date) // Long 형 시간 데이터를 지정된 형식으로 변환
        }
        return view
    }
 
 
    override fun getItemId(position: Int): Long { //리스트뷰를 클릭하여 이벤트 처리할 때 인자로 position,id 등이 넘어오게 된다. 
        //이때 넘어오는 id 값을 결정합니다 .데이터베이스를 다룰때 고유한 아이디로 처리하는데 그 값을 반환하도록 정의합니다.
        if(adapterData!=null){
            return adapterData!![position].id
        }else
        return super.getItemId(position)
 
    }
    class ViewHolder(view:View){
        val dateTextView : TextView =view.findViewById(R.id.text1)
        val textTextView : TextView =view.findViewById(R.id.text2)
    }

}



할 일 목록  표시

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        setSupportActionBar(toolbar)
 
 
 
        //전체 할일 정보를 가져와서 날짜 순으로 내림 차순 정렬
        val realmResult=realm.where<Todo>()
            .findAll()
            .sort("date", Sort.DESCENDING) //Sort.DESCENDING - 내림차순 , Sort.ASCENDING - 오름차순
 
        val adapter =TodoListAdapter(realmResult)
        listView.adapter=adapter
 
        //데이터가 변경되면 어댑터에 적용
        realmResult.addChangeListener {  _->adapter.notifyDataSetChanged() }
 
        listView.setOnItemClickListener { parent, view, position, id ->
            //할 일 수정
            startActivity<EditActivity>("id" to id)
        }
 
        //새 할일 추가
        addFab.setOnClickListener { view ->
            startActivity<EditActivity>()
        }
 
    }




댓글