읽기 전에

이 글은 안드로이드에서 기상청 날씨 API(동네예보) 사용법(retrofit) , Room을 활용한 데이터베이스 구축에 이어지는 글입니다. 따라서 아래 글을 읽지 않으신다면 이해가 힘든 부분이 있을 수 있습니다. 특히 Room을 활용한 데이터베이스 구축은 읽어보시기를 권장합니다.

 

이 글은 코드(Insert 쿼리 이용)로 직접 구현하여 엑셀 파일을 테이블에 넣는 과정을 설명하고 있습니다. 만약 Room과 같은 내부 데이터베이스가 아닌 외부 데이터베이스를 이용하신다면 엑셀 파일을 테이블화할 수 있는 기능을 제공할 확률이 높습니다. 있다면 그 방법을 추천드립니다. 

 

읽어올 파일이 텍스트파일이라면 각 속성이 특정 Delimeter로 구분되어 있어야 합니다. 예를 들어 아래 파일은 tab으로 구분되어 있습니다.

 

 

 

.xlsx to .txt

엑셀파일은 우선 텍스트파일로 변환이 필요하다.

엑셀파일을 열고 테이블에 삽입할 속성과 행만 남긴다. 특히 제목행은 없어야한다.

 

수정 전

 

수정 후

 

다른 이름으로 저장을 선택한 후 '텍스트(탭으로 분리)(*.txt)'로 파일형식을 변경한다.

 

 

 

Assets 폴더 생성

텍스트파일을 Assets 폴더에 넣고 이 폴더에서 파일을 읽어올 것이다. Assets폴더에 들어간 파일은 앱을 패키지화할때 같이 포함된다.

 

안드로이드 스튜디오에서 File -> New -> Folder -> Assets Folder를 선택하여 폴더를 생성한다.

 

 

assets 폴더가 생성된 것을 확인한 후, 윈도우 파일탐색기에서 텍스트 파일을 복사(ctrl+c)하고 assets 폴더를 선택하여 붙여넣기(ctrl+v)한다. 

 

 

텍스트파일이 assets폴더에 복사된 것을 확인할 수 있다.

 

 

 

만약 한글이 깨진다면 아래 글을 참고한다.

 

 

[Android] File was loaded in the wrong encoding: 'UTF-8' 오류

아래와 같이 파일의 한글이 깨지는 현상이 발생했다. 이를 해결하기 위해서는 위의 Reload in 'x-windows-949'를 선택하면 한글로 변환된다. 다만 이 방식은 인코딩 방식을 변경한다. 원래대로 UTF-8로 ��

hydroponicglass.tistory.com

 

 

파일 로드

AssetManager로 Assets에 있는 파일(NationalWeatherDB.txt)을 로드하고 InputStream으로 파일의 내용을 가져온다.

 

val assetManager: AssetManager = resources.assets
val inputStream: InputStream = assetManager.open("NationalWeatherDB.txt")

inputStream.bufferedReader().readLines().forEach {
    Log.d("file_test", it.toString()) // for test
}

 

입력 속도 향상을 위해 bufferedReader를 사용하고, readLines().forEach를 통해 파일의 매 라인을 읽어온다.

테스트를 위해 라인의 내용을 로그로 출력한다.

 

 

 

Tokenization

위의 텍스트파일은 각 속성이 탭(tab)으로 구분되어있다. 따라서 각 라인을 탭을 기준으로 Tokenization한다.

 

val assetManager: AssetManager = resources.assets
val inputStream: InputStream = assetManager.open("NationalWeatherDB.txt")

inputStream.bufferedReader().readLines().forEach {
    var token = it.split("\t")
    CoroutineScope(Dispatchers.Main).launch {
        Log.d("file_test", token.toString())
    }
}

 

로그를 통해 각 속성이 컴마(,)로 구분된 것을 확인할 수 있다.

 

 

 

테이블에 삽입

미리 만들어 둔 데이터베이스 테이블과 insert함수를 이용하여 파일의 내용을 테이블에 넣는다.

 

val assetManager: AssetManager = resources.assets
val inputStream: InputStream = assetManager.open("NationalWeatherDB.txt")

inputStream.bufferedReader().readLines().forEach {
    var token = it.split("\t")
    var input = NationalWeatherTable(token[0].toLong(), token[1], token[2], token[3], token[4].toInt(), token[5].toInt())
    CoroutineScope(Dispatchers.Main).launch {
        NationalWeatherDB.nationalWeatherInterface().insert(input)
    }
}

 

미리 만들어 둔 getAll 함수를 통해 "SELECT * FROM TABLE" 쿼리를 실행하면 아래와 같은 로그를 얻을 수 있다.

 

 

[NationalWeatherTable(code=1100000000, name1=서울특별시, name2=, name3=, x=60, y=127), NationalWeatherTable(code=1111000000, name1=서울특별시, name2=종로구, name3=, x=60, y=127), NationalWeatherTable(code=1111051500, name1=서울특별시, name2=종로구, name3=청운효자동, x=60, y=127), NationalWeatherTable(code=1111053000, name1=서울특별시, name2=종로구, name3=사직동, x=60, y=127),
.................. 이하 중략 ..............

 

테스트를 통해 insert쿼리를 여러 번 실행했다면 기본키 중복으로 오류가 발생할 가능성이 높다. 이 경우 미리 만들어 둔 deleteAll 함수를 먼저 실행하여 테이블을 초기화한다.

 

 

MainActivity.kt 전체코드

 

import android.content.res.AssetManager
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import android.util.Log
import androidx.room.*
import kotlinx.coroutines.*
import java.io.InputStream

@Entity
data class NationalWeatherTable(
    @PrimaryKey val code: Long,
    val name1: String,
    val name2: String,
    val name3: String,
    val x: Int,
    val y: Int
)

@Dao
interface NationalWeatherInterface {
    @Query("SELECT * FROM NationalWeatherTable")
    suspend fun getAll(): List<NationalWeatherTable>

    @Insert
    suspend fun insert(nationalWeatherTable: NationalWeatherTable)

    @Query("DELETE FROM NationalWeatherTable")
    suspend fun deleteAll()
}

@Database(entities = [NationalWeatherTable::class], version = 1)
abstract class AppDatabase: RoomDatabase() {
    abstract fun nationalWeatherInterface(): NationalWeatherInterface
}

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val NationalWeatherDB = Room.databaseBuilder(this, AppDatabase::class.java,"db").build()
        //val input = NationalWeatherTable(1114062500,"seoul", "jongrogu", "dasandong", 60, 126)

        val assetManager: AssetManager = resources.assets
        val inputStream: InputStream = assetManager.open("NationalWeatherDB.txt")

        inputStream.bufferedReader().readLines().forEach {
            var token = it.split("\t")
            var input = NationalWeatherTable(token[0].toLong(), token[1], token[2], token[3], token[4].toInt(), token[5].toInt())
            CoroutineScope(Dispatchers.Main).launch {
                NationalWeatherDB.nationalWeatherInterface().insert(input)
            }
            // Log.d("file_test", token.toString())
        }

        CoroutineScope(Dispatchers.Main).launch {
            //NationalWeatherDB.nationalWeatherInterface().deleteAll()
            //NationalWeatherDB.nationalWeatherInterface().insert(input)
            var output = NationalWeatherDB.nationalWeatherInterface().getAll()
            Log.d("db_test", "$output")
        }
    }
}