Realm 이용한 Database 사용 예제

|

프로젝트 build.gradle

buildscript {
    ext.kotlin_version = '1.3.31'
    repositories {
        google()
        jcenter()

    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.4.1'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        classpath "io.realm:realm-gradle-plugin:5.2.0"
    }
}


모듈 build.gradle

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
apply plugin: 'realm-android'

...

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


SnowApplication.kt

import android.app.Application
import android.content.Context
import io.realm.Realm
import io.realm.RealmConfiguration

class SnowApplication : Application() {

    companion object {
        private var instance: SnowApplication? = null

        fun context(): Context {
            return instance!!.applicationContext
        }
    }

    init {
        instance = this
    }

    override fun onCreate() {
        super.onCreate()

        Realm.init(this)
        Realm.setDefaultConfiguration(getRealmConfig())
    }

    private fun getRealmConfig(): RealmConfiguration {
        return RealmConfiguration.Builder()
                .deleteRealmIfMigrationNeeded()
                .build()
    }
}


데이터 레코드 정의

open 키워드를 이용해서 상속을 가능하게 해야 하며, RealmObject()를 상속받아야 합니다.

import android.os.Parcel
import android.os.Parcelable
import io.realm.RealmObject
import io.realm.annotations.PrimaryKey

open class RewardDto(
        var habitId: Long = 0,
        var year: Int = 0, var month: Int = 0, var day: Int = 0,
        var percent: Int = 0, var stampType: Int = 0) : RealmObject()

open class HabitDto(
        @PrimaryKey var id: Long = 0,
        var name: String = "", var seq: Long = 0) : RealmObject()


데이터 생성, 조회, 수정, 삭제

import com.snowdeer.htracker.model.data.HabitDto
import io.realm.Realm
import io.realm.Sort
import io.realm.kotlin.createObject
import io.realm.kotlin.where

interface OnDatabaseEventListener {
    fun onDatabaseUpdated()
}

class HabitModelManager {

    companion object {
        val instance = HabitModelManager()
    }

    private constructor()

    private val realm = Realm.getDefaultInstance()

    private var onDatabaseEventListener: OnDatabaseEventListener? = null

    fun setOnDatabaseEventListener(l: OnDatabaseEventListener) {
        onDatabaseEventListener = l
    }

    fun notifyDatabaseUpdated() {
        onDatabaseEventListener?.onDatabaseUpdated()
    }

    private fun nextId(): Long {
        val maxId = realm.where<HabitDto>().max("id")
        if (maxId != null) {
            return maxId.toLong() + 1
        }
        return 0
    }

    private fun nextSeq(): Long {
        val maxSeq = realm.where<HabitDto>().max("seq")
        return maxSeq?.toLong()?.plus(1) ?: 0
    }


    fun getList(): ArrayList<HabitDto> {
        val realmResult = realm.where<HabitDto>()
                .findAll()
                .sort("seq", Sort.DESCENDING)

        val list = ArrayList<HabitDto>()
        for (item in realmResult) {
            list.add(item)
        }

        return list
    }

    fun create(name: String) {
        realm.beginTransaction()

        val item = realm.createObject<HabitDto>(nextId())

        item.name = name
        item.seq = nextSeq()

        realm.commitTransaction()

        notifyDatabaseUpdated()
    }

    fun update(id: Long, name: String, seq: Long) {
        realm.beginTransaction()

        val item = realm.where<HabitDto>().equalTo("id", id).findFirst()!!
        item.name = name
        item.seq = seq

        realm.commitTransaction()

        notifyDatabaseUpdated()
    }

    fun deleteWithReward(id: Long) {
        realm.beginTransaction()

        val item = realm.where<HabitDto>().equalTo("id", id).findFirst()!!
        item.deleteFromRealm()

        realm.commitTransaction()

        notifyDatabaseUpdated()
    }
}

File에 텍스트(Text) 읽고 쓰기

|

File에 텍스트 읽고 쓰는 예제

</pre class=”prettyprint”> import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileReader; import java.io.FileWriter;

public class FileUtil {

public synchronized static void writeTextToFile(String filepath, String text) { File file = new File(filepath); FileWriter fw = null; BufferedWriter bw = null;

try {
  fw = new FileWriter(file);
  bw = new BufferedWriter(fw);
  bw.write(text);
} catch (Exception e) {
  e.printStackTrace();
} finally {
  try {
    bw.close();
  } catch (Exception e) {
    e.printStackTrace();
  }

  try {
    fw.close();
  } catch (Exception e) {
    e.printStackTrace();
  }
}   }

public synchronized static String getTextFromFile(String filepath) { File file = new File(filepath);

StringBuilder sb = new StringBuilder();

try {
  BufferedReader br = new BufferedReader(new FileReader(file));
  String line;

  while ((line = br.readLine()) != null) {
    sb.append(line);
    sb.append('\n');
  }
  br.close();
} catch (Exception e) {
  e.printStackTrace();
}

return sb.toString();   } }

</pre>

CollapsingToolbarLayout 예제

|

styles.xml


<resources>

<style name=”AppTheme” parent=”Theme.AppCompat.Light.DarkActionBar”> <item name=”colorPrimary”>#80CBC4</item> <item name=”colorPrimaryDark”>#80CBC4</item> <item name=”colorAccent”>#3F51B5</item> </style>

<style name=”SnowDeerTheme” parent=”AppTheme”> <item name=”windowNoTitle”>true</item> <item name=”windowActionBar”>false</item> </style>

</resources>


activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:app="http://schemas.android.com/apk/res-auto"
  xmlns:tools="http://schemas.android.com/tools"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  tools:context=".MainActivity">

  <android.support.design.widget.AppBarLayout
    android:id="@+id/appBarLayout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">

    <android.support.design.widget.CollapsingToolbarLayout
      android:id="@+id/collapsing_toolbar"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:fitsSystemWindows="true"
      app:contentScrim="@color/colorPrimary"
      app:expandedTitleMarginEnd="0dp"
      app:expandedTitleMarginStart="0dp"
      app:layout_scrollFlags="scroll|enterAlways|enterAlwaysCollapsed">

      <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="12dp"
        android:orientation="vertical"
        app:layout_collapseMode="parallax"
        app:layout_collapseParallaxMultiplier="0.7">

        <ImageView
          android:layout_width="64dp"
          android:layout_height="64dp"
          android:layout_gravity="center_horizontal"
          app:srcCompat="@drawable/ic_launcher"/>

        <TextView
          style="@style/TextAppearance.AppCompat.Widget.ActionBar.Title"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:layout_gravity="center_horizontal"
          android:text="@string/app_name"/>

      </LinearLayout>

      <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:popupTheme="@style/SnowDeerTheme"
        android:title="@string/app_name"
        app:layout_collapseMode="parallax"
        app:layout_scrollFlags="scroll|exitUntilCollapsed"/>

    </android.support.design.widget.CollapsingToolbarLayout>

    <RelativeLayout
      android:layout_width="match_parent"
      android:layout_height="wrap_content">

      <android.support.design.widget.TabLayout
        android:id="@+id/tabs"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:tabGravity="fill"
        app:tabMaxWidth="200dp"
        app:tabMode="scrollable"/>
    </RelativeLayout>
  </android.support.design.widget.AppBarLayout>

  <FrameLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_marginTop="8dp"
    android:layout_marginBottom="8dp"
    android:layout_marginStart="8dp"
    android:layout_marginEnd="8dp"
    app:layout_behavior="@string/appbar_scrolling_view_behavior">

    <android.support.v4.view.ViewPager
      android:id="@+id/viewpager"
      android:layout_width="match_parent"
      android:layout_height="match_parent"/>

  </FrameLayout>

</android.support.design.widget.CoordinatorLayout>

Firebase 회원 가입, 로그인 예제

|

회원 가입

import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.widget.Toast
import com.google.firebase.auth.FirebaseAuth
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

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

        create_userid_button.setOnClickListener {
            val email = email_input.text.toString()
            val password = password_input.text.toString()

            registerUser(email, password)
        }
    }

    private fun registerUser(email: String, password: String) {
        FirebaseAuth.getInstance().createUserWithEmailAndPassword(email, password)
            .addOnSuccessListener {
                val userId = FirebaseAuth.getInstance().currentUser
                Toast.makeText(applicationContext, "UserId(${userId?.email}) 생성 성공", Toast.LENGTH_SHORT).show()
            }
            .addOnFailureListener {
                it.printStackTrace()
                Toast.makeText(applicationContext, "UserId 생성 실패(${it.message})", Toast.LENGTH_SHORT).show()
            }
    }
}


로그인

import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.widget.Toast
import com.google.firebase.auth.FirebaseAuth
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

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

        login_button.setOnClickListener {
            val email = email_input.text.toString()
            val password = password_input.text.toString()

            login(email, password)
        }
    }

    private fun login(email: String, password: String) {
        FirebaseAuth.getInstance().signInWithEmailAndPassword(email, password)
            .addOnSuccessListener {
                val userId = FirebaseAuth.getInstance().currentUser
                Toast.makeText(applicationContext, "로그인 성공", Toast.LENGTH_SHORT).show()
            }
            .addOnFailureListener {
                it.printStackTrace()
                Toast.makeText(applicationContext, "로그인 실패(${it.message})", Toast.LENGTH_SHORT).show()
            }
    }
}


인증 메일 전송

인증 메일 양식은 Firebase Console에서 수정할 수 있습니다. 하지만 악용될 가능성이 있기 때문에 제목만 수정이 가능하며, 내요을 수정하지는 못합니다.

private fun verifyEmail() {
    val currentUser = FirebaseAuth.getInstance().currentUser
    currentUser?.let {
        it.sendEmailVerification()
            .addOnSuccessListener {
                Toast.makeText(applicationContext, "인증 메일을 전송했습니다.", Toast.LENGTH_SHORT).show()
            }
            .addOnFailureListener {
                it.printStackTrace()
                Toast.makeText(applicationContext, "인증 메일 전송 실패(${it.message})", Toast.LENGTH_SHORT).show()
            }
    }
}


Email 변경 및 패스워드 변경

Email 및 패스워드 변경은 다음 명령어를 이용해서 할 수 있습니다.

  • currentUser.updateEmail(newEmail: String)
  • currentUser.updatePassword(newPassword: String)

Kotlin Firebase Google SignIn 예제

|

build.gradle (프로젝트)

dependencies {
    classpath 'com.android.tools.build:gradle:3.4.0'
    classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    classpath 'com.google.gms:google-services:4.2.0'
}


build.gradle (모듈)

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    implementation 'com.android.support:appcompat-v7:28.0.0'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
    implementation 'com.google.firebase:firebase-auth:16.0.5'
    implementation 'com.google.firebase:firebase-database:16.0.4'

    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
    implementation 'com.google.android.gms:play-services:12.0.1'
    implementation 'com.google.android.gms:play-services-auth:16.0.1'
}


activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:orientation="vertical"
  tools:context=".MainActivity">

  <Button
    android:id="@+id/logout_button"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_centerInParent="true"
    android:text="logout"/>

</RelativeLayout>


activity_sign_in.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:orientation="vertical"
  tools:context=".MainActivity">

  <com.google.android.gms.common.SignInButton
    android:id="@+id/sign_in_button"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_centerInParent="true"/>

</RelativeLayout>


MainActivity.kt

import android.content.Intent
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import com.google.android.gms.auth.api.Auth
import com.google.android.gms.common.ConnectionResult
import com.google.android.gms.common.api.GoogleApiClient
import com.google.firebase.auth.FirebaseAuth
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity(), GoogleApiClient.OnConnectionFailedListener {
    override fun onConnectionFailed(p0: ConnectionResult) {
        TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
    }

    lateinit var googleApiClient: GoogleApiClient

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

        googleApiClient = GoogleApiClient.Builder(applicationContext)
                .enableAutoManage(this, this)
                .addApi(Auth.GOOGLE_SIGN_IN_API)
                .build()

        val currentUser = FirebaseAuth.getInstance().currentUser

        if (currentUser == null) {
            val intent = Intent(applicationContext, LoginActivity::class.java)
            startActivity(intent)
        } else {
            // TODO ...
        }

        logout_button.setOnClickListener {
            FirebaseAuth.getInstance().signOut()
            Auth.GoogleSignInApi.signOut(googleApiClient)

            val intent = Intent(applicationContext, LoginActivity::class.java)
            startActivity(intent)
        }
    }

    override fun onResume() {
        super.onResume()

        val currentUser = FirebaseAuth.getInstance().currentUser
        supportActionBar?.title = currentUser.toString()

    }
}


LoginActivity.kt

import android.content.Intent
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.widget.Toast
import com.google.android.gms.auth.api.Auth
import com.google.android.gms.auth.api.signin.GoogleSignIn
import com.google.android.gms.auth.api.signin.GoogleSignInOptions
import com.google.android.gms.common.ConnectionResult
import com.google.android.gms.common.api.GoogleApiClient
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.auth.GoogleAuthProvider
import kotlinx.android.synthetic.main.activity_login.*

class LoginActivity : AppCompatActivity(), GoogleApiClient.OnConnectionFailedListener {

    private val REQUEST_CODE_SIGN_IN = 1001

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

        val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
                .requestIdToken(getString(R.string.default_web_client_id))
                .requestEmail()
                .build()

        val googleSignInClient = GoogleSignIn.getClient(this, gso)

        login_button.setOnClickListener {
            val intent = googleSignInClient.signInIntent
            startActivityForResult(intent, REQUEST_CODE_SIGN_IN)
        }
    }

    override fun onConnectionFailed(result: ConnectionResult) {
        Toast.makeText(applicationContext, "연결 실패", Toast.LENGTH_SHORT).show()
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        if (requestCode == REQUEST_CODE_SIGN_IN) {
            val result = Auth.GoogleSignInApi.getSignInResultFromIntent(data)
            if (result.isSuccess) {
                val account = result.signInAccount
                val credential = GoogleAuthProvider.getCredential(account?.idToken, null)
                FirebaseAuth.getInstance().signInWithCredential(credential)
                        .addOnSuccessListener {
                            Toast.makeText(applicationContext, "인증 성공", Toast.LENGTH_SHORT).show()
                            startActivity(Intent(applicationContext, MainActivity::class.java))
                            finish()
                        }
                        .addOnFailureListener {
                            Toast.makeText(applicationContext, "인증 실패(${it.message})", Toast.LENGTH_SHORT).show()
                        }
            } else {
                Toast.makeText(applicationContext, "로그인 실패", Toast.LENGTH_SHORT).show()
            }
        }
    }
}