반응형

오늘은 코틀린(Kotlin)에서 XML을 사용하는 방법에 대해서 알아보겠습니다.

XML은 다양한 포맷을 지원하기 때문에 JSON 비해 느린 단점이 있지만 

일반적으로 사용하기 매우 편리한 저장 포맷입니다.

android에서는 외부 파일을 확인하기 위해서 일반적인 접근이 불가능합니다.

그래서 미리 만들어둔 XML 파일을 APK에 포함시켜 사용할 수 있는 방법을 적용해야 XML을 쉽게 접근할 수 있습니다.

android에서는 APK에 XML 파일을 포함할 수 있는 에셋(asset) 폴더를 생성해서 접근이 가능합니다.

안드로이드(android) 프로젝트를 생성하면 assets 폴더는 포함되어 있지 않습니다.

폴더 추가를 사용해서 assets 폴더를 생성하고 info.xml을 생성합니다.

<?xml version="1.0" encoding="utf-8"?>
<book>
   <bookinfo id="1111" name="책1번"  des="책1번은 요리책입니다." />
   <bookinfo id="1112" name="책2번"  des="책2번은 요리책입니다." />
   <bookinfo id="1113" name="책3번"  des="책3번은 요리책입니다." />
   <bookinfo id="1114" name="책4번"  des="책4번은 요리책입니다." />
</book>

간단하게 XML을 로드하기 위해서 4개의 로드를 생성합니다.

정상적으로 XML이 만들어지면 브라우저에서 트리 구조 파일을 확인할 수 있습니다.

코틀린(kotlin)에서 XML을 사용하기 위해서는 org.xmlpull.v1.XmlPullParser,

org.xmlpull.v1.XmlPullParserFactory 패키지를 포함해야 합니다.

먼저 InputStream을 사용해서 생성한 폴더 assets에 접근해서 XML 파일을 open 합니다.

XmlPullParserFactory를 사용해서 신규 인터페이스를 생성합니다.

XmlPullParser를 생성해서 factory와 연결 후 xml파일을 로드합니다.

var xmlinfo:InputStream = assets.open("info.xml")

var factory:XmlPullParserFactory = XmlPullParserFactory.newInstance()

var parser:XmlPullParser = factory.newPullParser()

parser.setInput( xmlinfo, null)

전체 XML 파일을 확인하기 위해서 타입을 확인하고 END_DOCUMENT까지 로드를 반복합니다.

설정한 tag는 "bookinfo"이므로 if 문에서 "bookinfo"일 경우만 getAttributeValue를 사용해서 하위 로드에 접근합니다.

getAttributeValue는 배열 형태로 Value 정보를 확인할 수 있습니다.

while( event != XmlPullParser.END_DOCUMENT){

                var tag_name = parser.name

                when(event){
                    XmlPullParser.END_TAG ->{
                        if( tag_name == "bookinfo")
                        {
                            var item:String = parser.getAttributeValue(0) + parser.getAttributeValue(1) + parser.getAttributeValue(2)
                            Toast.makeText(this, item, Toast.LENGTH_SHORT).show()
                            AddTextView(item)
                        }
                    }
                }
                event = parser.next()
            }

parser.next()를 사용해서 다음 로드로 이동하면 됩니다.

코틀린(kotlin)을 사용하면 XML을 쉽게 관리할 수 있어 매우 편리합니다.

다양한 외부 정보를 관리하기 위해서는 XML은 꼭 필요한 포맷이기 때문에 공부하시면 좋습니다.

컴파일하면 XML 로드와 동일하게 리스트를 생성합니다.

XML과 JSON을 많이 비교합니다.

JSON은 상대적으로 XML보다 빠르기 때문에 서버 데이터 전송에 많이 사용됩니다.

XML 장점은 UTF-8만 지원하는 JSON과 다르게 다양한 인코딩 형식을 지원하기 때문에 범용적으로 사용하기 좋습니다.

읽기/쓰기 속도에 큰 문제가 없는 프로그램이라면 XML 사용을 적극 추천드립니다.

감사합니다.

 

 

반응형
반응형

파일 탐색기를 사용하면 파일과 폴더 타입에 따라서 폴더 이동이 가능해야 합니다.

오늘은 코틀린(Kotlin)을 이용한 탐색기 세 번째 파트 파일 탐색기 이벤트 연동에 대해서 알아보겠습니다.

파일 탐색기 이벤트 연동은 파일, 폴더를 구분해서 재 탐색을 해야 합니다.

먼저 FilesListFragment에서 OnItemClickListener 이벤트 변수를 선언합니다.

아래쪽에 interface를 사용해서 onClick, onLogClick 함수를 선언합니다.

private lateinit var eCallback: OnItemClickListener

    interface OnItemClickListener{
        fun onClick(fileModel : FileModel)

        fun onLongClick(fileModel : FileModel)
    }

override onAttach를 선언하고 OnItemClickListener로 캐스팅하여 eCallback에 연결합니다.

onAttach는 실행 시점에 호출되기 때문에 오류를 방지하기 위해서 catch를 사용해서 오류를 정의합니다.

 override fun onAttach(context: Context?) {
        super.onAttach(context)

        try{
            eCallback = context as OnItemClickListener
        } catch( e: Exception){
            throw Exception("${context} FileListFragment onAttach")
        }
    }

FilesListFragment 클래스를 MainActivity에 상속합니다.

클래스명을 선언하면 OnItemClickListener 이벤트를 확인할 수 있습니다.

FilesListFragment 클래스는 interface로 onClick, onLongClick를 선언했기 때문에 상속을 받게 되면 interface를 선언해야 오류가 발생하지 않습니다.

onClick, onLongClick를 override하여 선언합니다.

onClick이벤트가 발생하면 container에 View를 실행하고 파일 정보를 넘겨줄 수 있는 AddFileFragment를 선언합니다.

	override fun onClick(fileModel: FileModel) {
       
    }

    override fun onLongClick(fileModel: FileModel) {
       
    }

    private fun AddFileFragment(fileModel: FileModel){
        val filesListFragment = FilesListFragment.build {
            path = fileModel.path
        }

        val fragmentTransaction = supportFragmentManager.beginTransaction()
        fragmentTransaction.replace(R.id.container, filesListFragment)
        fragmentTransaction.addToBackStack(fileModel.path)
        fragmentTransaction.commit()
    }

onClick 이벤트에 폴더일 경우 파일 경로를 저장하기 위해서 AddFileFragment를 연결합니다.

스택에 더 이상 확인할 내용이 없을 경우 종료할 수 있게 onBackPressed를 override 해줍니다.

override fun onBackPressed(){
        super.onBackPressed()
        if( supportFragmentManager.backStackEntryCount ==0){
            finish()
        }
    }

파일을 선택하면 파일 확장자에 연결된 앱을 실행하기 위해서 이벤트를 연동해야 합니다.

FileUtils.kt파일에 앱을 실행할 수 있는 함수 launchFileIntent를 선언합니다.

fun Context.launchFileIntent(fileModel: FileModel) {
    val intent = Intent(Intent.ACTION_VIEW)
    intent.data = FileProvider.getUriForFile(this, packageName, File(fileModel.path))
    intent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
    startActivity(Intent.createChooser(intent, "Select Application"))
}

launchFileintent 함수는 fileModel 경로에 확인된 확장자에 따라서 설치된 앱을 실행합니다.

폴더가 아닐 경우 launchFileIntent 함수를 사용해서 앱 실행을 적용합니다.

override fun onClick(fileModel: FileModel) {
        if( fileModel.fileType == FileType.FOLDER){
            AddFileFragment(fileModel)
        }else{
            launchFileIntent(fileModel)
        }
    }

onClick 함수는 폴더일 경우 AddFileFragment를 재 호출하여 리스트를 생성합니다.

파일 경우 launchFileIntent 함수를 사용해서 앱을 실행하고 뒤로 가기를 클릭하면 다시 폴더 리스트를 호출합니다.

마지막으로 InitViews함수에서 초기화 시점에 mFilesAdapter에 클릭 이벤트를 연동합니다.

mFilesAdapter.onItemClickListener = {
            eCallback.onClick(it)
        }

        mFilesAdapter.onItemLongClickListener = {
            eCallback.onLongClick(it)
        }

onItemClickListener, onItemLongClickListener 함수에 eCallback 이벤트를 연동하면 리스트 클릭 시점에 eCallback가 호출되면서 이벤트가 연동됩니다.

컴파일해서 실행하면 리스트에서 하위 폴더가 있을 경우 리스트가 변경되는 것을 확인할 수 있습니다.

폴더 하단에 아무것도 없을 경우는 "there is nothing here" View를 확인할 수 있습니다.

파일을 클릭하면 연결된 앱 실행 여부를 확인하고 앱을 실행할 수 있습니다.

탐색기를 사용한 이벤트 연동은 어렵게 보이지만, 기본은 간단한 이벤트 연동이기 때문에 조금만 보면 바로 확인할 수 있습니다.

다음 시간에는 파일 경로를 확인할 수 있는 로직을 추가해보겠습니다.

감사합니다.

 

 

http://thetechnocafe.com/build-a-file-explorer-in-kotlin-part-3-navigating-through-file-system/

 

Build a File Explorer in Kotlin – Part 3 – Navigating through File System - TheTechnoCafe

Welcome to the 3rd part of the Kotlin File Explorer series. Good job reading files from a path in the previous tutorial. Right now our application takes...

thetechnocafe.com

 

반응형
반응형

코틀린(Kotlin)을 사용 파일 탐색기 만들기는

다양한 컨트롤을 사용해서 개발 가능하지만

조금 더 자유롭게 리스트를 구성할 수 있는

RecyclerView를 사용해서 구현해보겠습니다.

 

RecyclerView는 인스타그램, 유튜브 피드, 전화번호부

등과 같이 동일한 형태의 뷰의 데이터에 따라서 자유롭게

구성할 수 있는 컨트롤입니다.

기존에는 ListView를 많이 사용했지만 커스터마이징이

힘든 단점이 부각되면서 RecyclerView를 많이 사용합니다.

 

먼저 파일 탐색기에서 가장 기본으로 사용하는

파일, 폴더 단위를 관리할 수 있는 Fragment를 구성해보겠습니다.

app 트리에서 filelist 패키지를 생성 후

FilesListFragment 코틀린(Kotlin) Class를 생성합니다.

class FilesListFragment : Fragment() {
   companion object {
        private const val ARG_PATH: String = "com.office.secuex"
        fun build(block:Builder.()-> Unit) = Builder().apply(block).build()
    }

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.fragment_files_list, container, false)
    }
}

companion object를 선언해서 블록안에

전역 변수를 지정합니다.

onCreateView 실행 시 infalter을 사용해서 레이아웃을 출력합니다.

inflater에 사용하기 위한 fragment_files_list.xml을 생성합니다.

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout  xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/filesRecyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScroolingViewBehavior" />

    <LinearLayout
        android:id="@+id/emptyFolderLayout"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:gravity="center"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <ImageView
            android:layout_width="200dp"
            android:layout_height="200dp"
            android:background="@drawable/background_circle"
            android:padding="40dp"
            android:src="@drawable/ic_folder_dark_24dp" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_marginTop="16dp"
            android:layout_height="wrap_content"
            android:textSize="16sp"
            android:textStyle="bold"
            android:text="There is nothing here!"/>

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="12sp"
            android:text="Empty Folder."/>

    </LinearLayout>

</androidx.constraintlayout.widget.ConstraintLayout >

RectclerView 속성인 app:layout_hehavior은

최신 버전 형태로 변경해야 합니다.

build.gradle app 항목에 아래 내용을 추가합니다.

implementation("androidx.recyclerview:recyclerview:1.2.1")
implementation("androidx.recyclerview:recyclerview-selection:1.1.0")

패키지를 추가하면 RecyClerView를 사용할 수 있습니다.

아이템 정보를 연결할 수 있는 빌더 클래스를

FileListFragment에 추가합니다.

    class Builder{
        var path : String = ""
        fun build(): FilesListFragment{
            val fragment = FilesListFragment()
            val args = Bundle()
            args.putString(ARG_PATH, path)
            fragment.arguments = args;
            return fragment
        }
    }

모든 변수에 연결할 수 있는 Builder 클래스는

입력받은 FilesListFragment 정보를 연결합니다.

이제 생성한 FilesListFragment를 MainActivity에

연결해야 합니다.

 

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

        if( savedInstanceState == null)
        {
            val filesListFragment = FilesListFragment.build {
                path = Environment.getExternalStorageDirectory().absolutePath
            }

            supportFragmentManager.beginTransaction()
                .add( R.id.container, filesListFragment)
                .addToBackStack( Environment.getExternalStorageDirectory().absolutePath)
                .commit()
        }

savedInstanceState가 없을 경우

filesListFragment를 생성해서 백그라운드로 실행시킵니다.

MainActivity 레이아웃에 FrameLayout를 생성합니다.

 <FrameLayout
        app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScroolingViewBehavior"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/container">
    </FrameLayout>

FrameLayout 속성 중 app:layout_behavior을

최신 버전 형태로 변경합니다.

FrameLayout를 사용해서 Activity가 실행되면

RecyclerView를 실행할 수 있는 기본 구조를 모두 확인했습니다.

이번에는 RecyclerView에 들어갈 파일, 폴더 정보를

정리해보겠습니다.

common 폴더를 생성하고 FileType.kt 파일을 생성합니다.

파일 타입, 파일 모델을 확인할 수 있는 두 개의

클래스를 정의합니다.

package com.office.secuex.common

import java.io.File

enum class FileType {
    FILE,
    FOLDER;

    companion object{
        fun getFileType(file: File) = when(file.isDirectory){
            true -> FOLDER
            false -> FILE
        }
    }

}

data class FileModel(
    val path : String,
    val fileType : FileType,
    val name : String,
    val sizeInMB: Double,
    val extension: String ="",
    val subFiles: Int = 0
)

FileModel은 경로, 타입, 이름

사이즈, 확장자로 구분됩니다.

파일 경로, 용량, 파일 모델을 구성하기 위한

함수를 utils 패키지를 생성해서 정의합니다.

package com.office.secuex.utils

import com.office.secuex.common.FileModel
import com.office.secuex.common.FileType
import java.io.File

fun getFilesFromPath( path : String, showHiddenFiles : Boolean = false, onlyFolders:Boolean = false)
        : List<File>{
    val file = File(path)
    return file.listFiles()
        .filter { showHiddenFiles || !it.name.startsWith(".")}
        .filter { !onlyFolders || it.isDirectory}
        .toList()
}

fun getFileModelsFromFiles(files: List<File>) : List<FileModel>{
    return files.map {
        FileModel(it.path, FileType.getFileType(it), it.name,
        convertFileSizeToMB(it.length()), it.extension, it.listFiles()?.size?:0)
    }
}

fun convertFileSizeToMB(sizeInBytes: Long) : Double{
    return ( sizeInBytes.toDouble()) / (1024 * 1024)
}

getFilesFromPath() 함수를 사용해서

파일 리스트를 확인할 수 있습니다.

FilesListFragment에서 사용하는

RectclerView에 연결하기 위한

item_recycler_file.xml 레이아웃을 생성합니다.

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout  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="wrap_content"
    android:layout_marginBottom="1dp"
    android:background="@color/colorPrimary"
    android:foreground="?selectableItemBackground"
    android:padding="16dp">

    <TextView
        android:id="@+id/nameTextView"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:textSize="20sp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        tools:text="Pictuers" />

    <TextView
        android:id="@+id/folderTextView"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:text="(Folder)"
        android:textSize="12sp"
        android:visibility="gone"
        app:layout_constraintLeft_toLeftOf="@id/nameTextView"
        app:layout_constraintRight_toRightOf="@id/nameTextView"
        app:layout_constraintTop_toBottomOf="@id/nameTextView" />

    <TextView
        android:id="@+id/totalSizeTextView"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:text="4 MB"
        android:textSize="12sp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@id/folderTextView" />

</androidx.constraintlayout.widget.ConstraintLayout >

하단에 있는 참고 사이트와는 버전이 다르기 때문에

최신 버전은 ConstraintLayout를 사용해야 최신 버전에서

컴파일이 정상적으로 진행됩니다.

이제 RecyclerView에 정보를 연결할 수 있는

FilesRecyclerAdapter를 만들어 보겠습니다.

filelist 패키지 아래에 FilesRecyclerAdapter 클래스를 생성합니다.

package com.office.secuex.filelist

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.office.secuex.R
import com.office.secuex.common.FileModel
import com.office.secuex.common.FileType
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.item_recycler_file.view.*

class FilesRecyclerAdapter : RecyclerView.Adapter<FilesRecyclerAdapter.ViewHolder>() {

    var onItemClickListener: ((FileModel) -> Unit)? = null
    var onItemLongClickListener: ((FileModel) -> Unit)? = null

    var filesList = listOf<FileModel>()

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val view = LayoutInflater.from(parent.context).inflate(R.layout.item_recycler_file, parent, false)
        return ViewHolder(view)
    }

    override fun getItemCount() = filesList.size

    override fun onBindViewHolder(holder: ViewHolder, position: Int) = holder.bindView(position)

    fun updateData(filesList: List<FileModel>) {
        this.filesList = filesList
        notifyDataSetChanged()
    }

    inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), View.OnClickListener, View.OnLongClickListener {
        init {
            itemView.setOnClickListener(this)
            itemView.setOnLongClickListener(this)
        }

        override fun onClick(v: View?) {
            onItemClickListener?.invoke(filesList[adapterPosition])
        }

        override fun onLongClick(v: View?): Boolean {
            onItemLongClickListener?.invoke(filesList[adapterPosition])
            return true
        }

        fun bindView(position: Int) {
            val fileModel = filesList[position]
            itemView.nameTextView.text = fileModel.name

            if (fileModel.fileType == FileType.FOLDER) {
                itemView.folderTextView.visibility = View.VISIBLE
                itemView.totalSizeTextView.visibility = View.GONE
                itemView.folderTextView.text = "(${fileModel.subFiles} files)"
            } else {
                itemView.folderTextView.visibility = View.GONE
                itemView.totalSizeTextView.visibility = View.VISIBLE
                itemView.totalSizeTextView.text = "${String.format("%.2f", fileModel.sizeInMB)} mb"
            }
        }
    }
}

RectclerView.adapter을 사용해서

파일 및 폴더 정보를 접근할 수 있습니다.

마지막으로 MainActivity에 연결된

FilesListFragment에 Adapter를 연결합니다.

package com.office.secuex.filelist

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.office.secuex.R
import com.office.secuex.common.FileModel
import com.office.secuex.common.FileType
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.item_recycler_file.view.*

class FilesRecyclerAdapter : RecyclerView.Adapter<FilesRecyclerAdapter.ViewHolder>() {

    var onItemClickListener: ((FileModel) -> Unit)? = null
    var onItemLongClickListener: ((FileModel) -> Unit)? = null

    var filesList = listOf<FileModel>()

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val view = LayoutInflater.from(parent.context).inflate(R.layout.item_recycler_file, parent, false)
        return ViewHolder(view)
    }

    override fun getItemCount() = filesList.size

    override fun onBindViewHolder(holder: ViewHolder, position: Int) = holder.bindView(position)

    fun updateData(filesList: List<FileModel>) {
        this.filesList = filesList
        notifyDataSetChanged()
    }

    inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), View.OnClickListener, View.OnLongClickListener {
        init {
            itemView.setOnClickListener(this)
            itemView.setOnLongClickListener(this)
        }

        override fun onClick(v: View?) {
            onItemClickListener?.invoke(filesList[adapterPosition])
        }

        override fun onLongClick(v: View?): Boolean {
            onItemLongClickListener?.invoke(filesList[adapterPosition])
            return true
        }

        fun bindView(position: Int) {
            val fileModel = filesList[position]
            itemView.nameTextView.text = fileModel.name

            if (fileModel.fileType == FileType.FOLDER) {
                itemView.folderTextView.visibility = View.VISIBLE
                itemView.totalSizeTextView.visibility = View.GONE
                itemView.folderTextView.text = "(${fileModel.subFiles} files)"
            } else {
                itemView.folderTextView.visibility = View.GONE
                itemView.totalSizeTextView.visibility = View.VISIBLE
                itemView.totalSizeTextView.text = "${String.format("%.2f", fileModel.sizeInMB)} mb"
            }
        }
    }
}

컴파일을 진행하면 정상적으로 컴파일이 되면서

RecyclerView를 확인할 수 있어야 합니다.

그런데 Error가 발생하면서 컴파일이 안됩니다.

Error 내용은 파일에 접근을 할 수 없는 내용입니다.

스토리지 접근에 필요한 정보를 Manifest.xml에 추가했습니다.

 <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

권한을 추가해도 오류가 동일하게 발생합니다.

확인 결과 Application에 추가 권한 필요했습니다.

Application에 추가적으로 아래 속성을 추가합니다.

 android:requestLegacyExternalStorage="true"

자 그럼 다시 한번 컴파일을 해보겠습니다.

기본 스토리지 정보가 List에 출력되는 것을 확인할 수 있습니다.

코틀린(Kotlin)을 사용해서 리스트를 출력하기

위해서는 많은 코딩이 필요하지만

기본 내용을 이해하면 대부분 같은 패턴을 구성되기 때문에

한 번은 꼭 직접 코딩을 하면서 확인해주십시오.

저도 참고 내용을 확인하면서 진행했지만

버전 문제 따른 다양한 오류를 경험했습니다.

오늘은 코틀린(Kotlin)을 사용해서 파일 탐색기

기본 UI를 만들었습니다.

다음 시간에는 RecyclerView에서 이벤트

연동을 해보겠습니다.

감사합니다.

http://thetechnocafe.com/build-a-file-explorer-in-kotlin-part-2-reading-files-from-paths/

 

Build a File Explorer in Kotlin – Part 2 – Reading files from paths - TheTechnoCafe

In this tutorial you will read the list of files/folders on a given path and display them in a RecyclerView along with their size and meta data such as...

thetechnocafe.com

 

반응형
반응형

스마트폰을 사용해서 다양한 파일을

관리하기 위해서 파일 탐색기를 많이 사용합니다.

파일 탐색기는 저장된 스토리지

정보를 모두 확인하기 위한 다양한 기술이

필요하기 때문에 프로그램 개발에서는

반드시 알아야 하는 프로그램입니다.

 

오늘은 코틀린(Kotlin)을 사용한 파일, 폴더를

관리할 수 있는 파일 탐색기(explorer)를

알아보겠습니다.

 

일반적인 파일 탐색기 보다는 좋은 예제 소스가 있어

참고 예제 소스 기준으로 몇 회로 나누어 알아보겠습니다.

참고 사이트는 아래쪽에 링크를 걸었습니다.

 

먼저 안드로이드(Android)에서 파일 탐색을

하기 위한 Provider에 대해서 알아보겠습니다.

 

안드로이드는 Acrivity, Service,

Broadcast Receiver, Content Provider

4가지 구성요소로 구분됩니다.

 

Content Provider는 앱이 접근할 수 있는

모든 영구 저장 위치에 저장 가능한 앱 데이터의

공유형 집합을 관리합니다.

파일 시스템, SQLite, 데이터 관리

파일 읽기, 쓰기, 수정하기, 삭제하기 기능 관리

데이터 엑세스 권한에 대한 제어 기능

즉 파일을 접근하기 위해서는 Provider를 먼저 생성해야 합니다.

코틀린(Kotlin) 신규 프로젝트를 생성합니다.

AndroidManifest.xml에 스토리지

읽기, 쓰기 권한을 설정합니다.

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

Provider을 사용하기 위해서 코틀린(Kotlin)

Class를 생성합니다.

Kotlin File/Class 메뉴를 선택 후

GenericFileProvider Class를 생성합니다.

package com.office.secuex
import androidx.core.content.FileProvider
class GenericFileProvider : FileProvider()

Provider path를 연결하기 위해서 package 이름을 추가하고

탐색기(Explorer)에 사용할 FileProvider을 상속받습니다.

Provider을 연결하기 위한

provider_paths.xml을 생성합니다.

<paths xmlns:android="http://schemas.android.com/apk/res-auto">
    <external-path
        name="external_files"
        path="."/>
</paths>

xml 정보를 입력하고 경로를 확인합니다.

provider을 연결하기 위해서

Manifest.xml 파일 하단에 provider 정보를

입력합니다.

        <provider
            android:authorities="com.office.secuex"
            android:name=".GenericFileProvider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/provider_paths"/>
        </provider>

autorities 속성은 생성한 package 이름으로 변경

name 속성은 생성한 Provider 파일 명

resource 속성은 xml 경로입니다.

 

코틀린(Kotlin) Provider를 설정 후 빌드를

진행하면 정상적으로 오류 없이 빌드됩니다.

오류가 발생할 경우 대부분 provider 속성 오류입니다.

 

다음 시간에는 Provider을 사용해서

파일 리스트를 생성해보겠습니다.

감사합니다.

http://thetechnocafe.com/build-a-file-explorer-in-kotlin-part-1-introduction-and-set-up/

 

Build a File Explorer in Kotlin - Part 1 - Introduction and Set up - TheTechnoCafe

This tutorial series will guide you through building a simple Android File Explorer application in Kotlin. You will develop a file explorer application...

thetechnocafe.com

 

반응형
반응형

지난 시간에 카메라를 사용해서 이미지 뷰에 연결하는 방법을 공부했습니다.

카메라 캡처를 사용해서 이미지를 사용하면 해상도가 떨어지기 때문에 원본이 손실되는 문제가 발생했습니다.

이번 시간에는 원본 이미지 손실을 최소화할 수 있는 이미지 저장 방법에 대해서 알아보겠습니다.

지난 시간에 배운 코틀린을 이용한 카메라 이미지 저장 실행 화면입니다.

원본 이미지 해상도가 많이 떨어지는 것을 확인할 수 있습니다.

해상도를 유지하기 위해서 먼저 촬영된 이미지를 원본 그대로 갤러리에 저장 후 로드하는 형태로 변경해보겠습니다.

카메라 원보 이미지를 저장하기 위해서 먼저 이미지 경로 Uri를 생성합니다.

fun createImageUri(filename:String, mimeType:String):Uri?{
        var values = ContentValues()
        values.put(MediaStore.Images.Media.DISPLAY_NAME,filename)
        values.put(MediaStore.Images.Media.MIME_TYPE, mimeType)
        return contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
    }

ContentValues를 사용해서 이미지 Uri를 생성합니다.

contentResolver는 contentProvider과 비즈니스 로직의 중계자 역할을 담당합니다.

ContentValues는 contentResolver이 사용하는 데이터 정보라고 생각하면 됩니다.

ContentValues에 이미지 이름과 타입을 저장합니다.

카메라를 동작하기 위해서 dispatchTakePictureIntentEx 함수를 추가합니다.

private var photoURI : Uri? = null
    private val REQUEST_CREATE_EX = 3

    private fun dispatchTakePictureIntentEx()
    {
        val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmmss").format(Date())
        val storageDir: File? = getExternalFilesDir(Environment.DIRECTORY_PICTURES)
        val takePictureIntent : Intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
        val uri : Uri? =   createImageUri("JPEG_${timeStamp}_", "image/jpg")
        photoURI = uri
        takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI)
        startActivityForResult(takePictureIntent, REQUEST_CREATE_EX)
    }

전역 경로 photoURI, 이벤트 ID REQUEST_CREATE_EX를 선언합니다.

이전 시간에 선언한 함수와 동일하 구성이기 때문에 Ex를 붙여서 이름을 변경했습니다.

timeStamp를 사용해서 이미지 이름을 시간에 따라서 생성합니다.

CreateimageUri 함수를 호출해서 이미지를 생성하고 카메라를 실행합니다.

fun loadBitmapFromMediaStoreBy(photoUri: Uri) : Bitmap?{
        var image: Bitmap? = null
        try{
            image = if(Build.VERSION.SDK_INT > 27){
                val source: ImageDecoder.Source =
                    ImageDecoder.createSource(this.contentResolver, photoUri)
                ImageDecoder.decodeBitmap(source)

            }else{
                MediaStore.Images.Media.getBitmap(this.contentResolver, photoUri)
            }
        }catch(e:IOException){
            e.printStackTrace()
        }
        return image
    }

생성된 Uri 경로에 이미지를 MediaStore를 사용해서 읽어옵니다.

 btnCamera.setOnClickListener{
                if(checkPermission()){
                    //dispatchTakePictureIntent()
                    dispatchTakePictureIntentEx()
                }
            else{
                    requestPermission()
                }
        }

기존에 연결되어 있던 버튼 이벤트에서 신규로 선언한 dispatchTakePictureIntentEx 함수를 실행합니다.

else if( requestCode == REQUEST_CREATE_EX)
{
    if( photoURI != null)
    {
           val bitmap = loadBitmapFromMediaStoreBy(photoURI!!)
           GImageView.setImageBitmap(bitmap)
           photoURI = null
    }
}

onActivityResult 함수에서 이벤트 ID REQUEST_CREATE_EX를 필터링 후

이미지를 로드하는 loadBitmapFromMediaStoreBy 함수를 호출해서 이미지 뷰에 연결합니다.

카메라 촬영 후 갤러리를 확인하면 카메라 이미지를 확인할 수 있습니다.

원본 이미지를 사용해서 이미지뷰에 연결하면 해상도가 동일한 것을 확인할 수 있습니다.

카메라 이미지 해상도를 원본과 동일하게하기 위해서 별도 저장 후 로드하는 방법을 사용하게 가장 좋은 방법입니다.

이미지 정보가 시간에 따라서 변경되기 때문에 이름을 고정하면 한 개의 사진만 업데이트됩니다.

코틀린을 사용하면 코드가 매우 간결하기 때문에 매우 편리합니다.

객체를 주고받을 때 NULL 존재하기 때문에 ?를 사용하는 부분만 확인하면 어렵지 않은 코드입니다.

감사합니다.

 

반응형
반응형

오늘은 코틀린(Kotlin)을 사용해서 안드로이드 카메라 이벤트 이미지를 이미지 뷰에 출력해보겠습니다.

안드로이드 카메라를 사용하기 위해서 먼저 AndroidMainfest.xml에 속성을 추가해주세요.

<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-feature android:name="android.hardware.camera" android:required="true" />

 "required" 속성이 true일 경우는 반드시 카메라 사용 옵션입니다.

경우에 따라서 설정을 변경하면 됩니다.

카메라를 실행하기 위해서 activity_main.xml에 button을 추가합니다.

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    >
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:orientation="horizontal">

        <Button
            android:id="@+id/btnGallery"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Gallery"
            tools:layout_editor_absoluteX="62dp"
            tools:layout_editor_absoluteY="16dp" />

        <Button
            android:id="@+id/btnCamera"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Camera"
            tools:layout_editor_absoluteY="76dp" />
    </LinearLayout>


    <ImageView
        android:id="@+id/GImageView"
        android:layout_width="match_parent"
        android:layout_height="500dp"
        tools:layout_editor_absoluteX="16dp"
        tools:layout_editor_absoluteY="124dp" />

    </LinearLayout>
    

</androidx.constraintlayout.widget.ConstraintLayout>

xml 추가 후 빌드하면 버튼이 정렬되어 출력됩니다.

"CAMERA" 버튼을 클릭하면 카메라를 실행하고, 촬영된 이미지를 하단 이미지 뷰에 추가하겠습니다.

카메라를 사용하기 위해서는 먼저 권한을 승인받아야 합니다.

 private fun requestPermission(){
        ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE,CAMERA),1)

    }
    private fun checkPermission():Boolean{

        return (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
                == PackageManager.PERMISSION_GRANTED && ContextCompat.checkSelfPermission(this,
        Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED)

    }
    @Override
    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<out String>,
        grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)

        if( requestCode == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED){
            Toast.makeText(this, "권한 설정 OK", Toast.LENGTH_SHORT).show()
        }
        else
        {
            Toast.makeText(this, "권한 허용 안됨", Toast.LENGTH_SHORT).show()
        }
    }

ActivityCompat Class를 사용해서 카메라 사용 권한을 요청합니다.

checkPermission() 함수는 권한 여부를 확인할 수 있습니다.

onRequestPermissionsResult() 함수를 override 후 권한 승인에 따른 이벤트를 확인할 수 있습니다.

즉 권한이 없을 경우 앱 동작을 중단하거나, 메시지를 출력할 수 있습니다.

카메라를 실행하기 위해서 Intent를 실행합니다.

    private val REQUEST_IMAGE_CAPTURE = 2
    private fun dispatchTakePictureIntent() {
        Intent(MediaStore.ACTION_IMAGE_CAPTURE).also { takePictureIntent ->
            takePictureIntent.resolveActivity(packageManager)?.also {
                startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE)
            }
        }
    }

권한이 승인되면 MediaStore.ACTION_IMAGE_CAPTURE argument를 사용해서 Intent를 실행합니다.

이젠 버튼 이벤트를 연결해서 dispatchTakePictureIntent() 함수를 실행합니다.

btnCamera.setOnClickListener{
                if(checkPermission()){
                    dispatchTakePictureIntent()
                }
            else{
                    requestPermission()
                }
        }

button event에서 checkPermission() 함수를 실행 후 권한이 없다면 권한을 재 요청합니다.

권한 승인 되었다면 카메라를 실행합니다.

정상적으로 카메라 실행 화면을 확인할 수 있습니다.

카메라 촬영 버튼을 클릭하면 이미지가 저장되면서 이벤트가 발생합니다.

이전 시간에 배운 onActivityResult 이벤트를 사용해서 이미지를 저장합니다.

@Override
    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)

        if( resultCode == Activity.RESULT_OK) {
            if (requestCode == GALLERY) {
                var ImnageData: Uri? = data?.data
                Toast.makeText(this, ImnageData.toString(), Toast.LENGTH_SHORT).show()
                try {
                    val bitmap = MediaStore.Images.Media.getBitmap(contentResolver, ImnageData)
                    GImageView.setImageBitmap(bitmap)
                } catch (e: Exception) {
                    e.printStackTrace()
                }
            }
            else if( requestCode == REQUEST_IMAGE_CAPTURE)
            {
                val imageBitmap :Bitmap? = data?.extras?.get("data") as Bitmap
                GImageView.setImageBitmap(imageBitmap)
            }
        }
    }

requestCode가 REQUEST_IMAGE_CAPTURE일 경우 "data" 이미지를 이미지 뷰에 연결할 수 있습니다.

Intent를 사용한 뷰 이벤트는 대부분 onActivityResult를 사용해서 필터링이 가능합니다.

촬영 후 정상적으로 이미지 뷰에 촬영한 사진이 출력됩니다.

일반적인 카메라 이미지를 사용할 경우는 Intent를 사용하면 매우 편리합니다.

하지만, 카메라 기능을 제어할 수 없기 때문에 카메라 기능을 사용할 경우는 별도 뷰를 개발해야 합니다.

감사합니다.

https://developer.android.com/training/camera/photobasics?hl=ko

 

사진 촬영  |  Android 개발자  |  Android Developers

이 과정에서는 기존 카메라 애플리케이션을 사용하여 사진을 캡처하는 방법을 설명합니다. 클라이언트 앱을 실행하는 기기에서 촬영한 하늘 사진을 조합하여 세계 날씨 지도를 만드는 크라우

developer.android.com

 

반응형
반응형

오늘은 코틀린(Kotlin)을 사용해서 캘러리 뷰 이미지 클릭 이벤트에 대해서 알아보겠습니다.

먼저 layout을 변경하기 위해서 mainActivity.xml을 클릭합니다.

 <Button
        android:text="Gallery"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/btnGallery"   />

    <ImageView
        android:layout_width="match_parent"
        android:layout_height="500dp"
        android:id="@+id/GImageView" />

갤러리를 호출하기 위한 Button과 선택한 이미지를 출력하기 위한 ImageView를 선언합니다.

ImageView 크기는 원하는 크기로 설정하면 됩니다.

빌드하면 GALLERY BUTTON만 확인할 수 있습니다.

이제 MainActivity에서 버튼 이벤트를 생성해서 갤러리 호출 이벤트 연결이 필요합니다.

    private val GALLERY = 1
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        btnGallery.setOnClickListener{
            val intent: Intent = Intent(Intent.ACTION_GET_CONTENT)
            intent.setType("image/*")
            startActivityForResult(intent,GALLERY)
        }

    }

전역 변수로 GALLERY = 1로 정의해주세요.

Intent 갤러리 타입은 1이기 때문에 편하게 사용하기 위해서 정의하는 겁니다.

layout에서 선언한 button에 setOnClickListener 이벤트를 연결합니다.

갤러리는 Intent를 사용해서 호출합니다.

전체 이미지를 확인하기 위해서 "Image/*"로 정의합니다.

코드 작성 후 빌드하면 button 클릭 시 갤러리 화면을 확인할 수 있습니다.

이제 갤러리 이미지에서 이벤트를 연결하겠습니다.

 @Override
    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)

        if( resultCode == Activity.RESULT_OK){
            if( requestCode ==  GALLERY)
            {
                var ImnageData: Uri? = data?.data
                Toast.makeText(this,ImnageData.toString(), Toast.LENGTH_SHORT ).show()
                try {
                    val bitmap = MediaStore.Images.Media.getBitmap(contentResolver, ImnageData)
                    GImageView.setImageBitmap(bitmap)
                }
                catch (e:Exception)
                {
                    e.printStackTrace()
                }
            }
        }
    }

onActivityResult 함수를 override 해줍니다.

onActivityResult 함수는 호출된 Activity 이벤트를 확인할 수 있습니다.

resultCode는 Activity 완료 이벤트를 확인할 수 있습니다.

requestCode는 호출 Code를 확인할 수 있습니다.

data에서는 현재 클릭한 Object URL 경로 확인이 가능합니다.

MediaStore 객체를 사용해서 URL 경로에 이미지를 Bitmap로 변경합니다.

마지막으로 Layout에 정의된 GImageView 객체에 setImageBitmap를 사용해서 BitMap을 출력합니다.

출력 결과 선택한 이미지를 ImageView에서 확인할 수 있습니다.

Intent를 사용해서 간단하게 갤러리 이미지를 확인할 수 있지만,

갤러리 뷰를 마음대로 조절할 수 없기 때문에 디바이스 종속적으로 사용만 가능합니다.

상기 기능은 갤러리에서 이미지를 선택할 경우 사용하면 매우 편리합니다.

감사합니다.

 

 

반응형
반응형

코틀린(Kotlin)도 다른 언어와 동일하게 함수를 사용할 수 있습니다.

코틀린에 가장 큰 함수 특징은 일급 객체로 사용이 가능합니다.

일급 객체는 일반적인 함수 형식과  엔티티로 적용하여 변수로 바로 사용이 가능하다.

함수 기본 형식은 (함수명) -> 반환 형식으로 되어 있습니다.

fun MakeString(mes : String): String{
    return mes + "  Create Message"
}

MakeString 함수는 mes String 매개변수를 받아서 String으로 반환합니다.

println(MakeString("함수 사용"))

println 함수를 사용해서 출력합니다.

출력 결과 입력된 "함수 사용" 뒤에 메시지가 추가되었습니다.

일반적인 함수는 함수를 만들어서 호출할 수 있습니다.

코틀린(Kotlin) 함수는 이런 함수를 변수 처럼 정의할 수 있습니다.

var MString:(String) -> String = fun(mes:String) = mes + " Create Message 2"

변수 MString에 함수를 직접 선언할 수 있습니다.

    println(MakeString("함수 사용"))

    println(MString("변수 함수 사용"))

MakeString 함수와 MString 변수를 출력 했습니다.

출력 결과 동일하게 입력 String에 추가 내용이 포함되어 있습니다.

함수에 사용되는 매개변수를 다양하게 사용이 가능합니다.

    var MString:(String) -> String = fun(mes:String) = mes + " Create Message 2"

    var a:(Int) -> Int = fun(i:Int) = i * 10

    println(a(7).toString())

    println(MakeString("함수 사용"))

    println(MString("변수 함수 사용"))

Int를 사용해서 추가 함수 a를 선언했습니다.

기존과 동일하게 println 함수를 사용해서 출력했습니다.

a 함수는 입력 변수에 10을 곱하는 형태로 7을 입력 시 70을 반환했습니다.

코틀린 함수는 매개변수 형식과 반환 형식을 자유롭게 사용할 수 있어 문법이 매우 편리합니다.

또한 외부 함수 정의가 아닌 내부에 함수를 정의를 사용할 수 있어 객체에 대한 처리를 완벽하게 수용할 수 있습니다.

내부 함수 정의 후 모호하다면 invoke() 메서드를 사용해서 모호함을 해결할 수 있다.

감사합니다.

반응형
반응형

코틀린이나 자바로 클래스를 정의할 경우 클래스 내부에 클래스를 다시 정의해야 하는 경우가 있습니다.

코틀린은 자바와 동일하게 최상위 클래스 내부에 클래스를 다시 배치할 수 있는 중첩 클래스(neted class)를 지원합니다.

중첩 클래스(neted class)

class nested{
    private val value:Int = 0

    class In{
        fun foo() = 7
    }
}

클래스 내부에 함수를 다른 클래스에 정의해서 접근할 수 있습니다.

nested class 내부에 In class를 정의하고 foo 메서드를 사용해서 멤버에 접근이 가능합니다.

nested.In().foo() 메서드를 사용해서 멤버 정보를 출력할 수 있습니다.

출력 결과 멤버에 저장된 7을 출력하게 됩니다.

중첩 클래스(neted class)에서 최상위 클래스에 정의된 인스턴스 변수나 메서드에 직접 접근이 불가능합니다.

중첩된 내부 클래스를 사용해서 객체 참조가 가능합니다.

class nested{
    private val value:Int = 0

    class In{
        fun foo() = 7
    }

    inner class innerIn{
        fun foo2() = 20
    }
}

코틀린(Kotlin) 중첩 클래스(neted class)에서 내부 클래스를 직접 접근하기 위해서는 inner 한정자를 사용해서 클래스를 정의하면 됩니다.

innerIn class는 inner로 정의 되었기 때문에 직접 foo2 메서드에 접근이 가능합니다.

nested를 선언하고 innerIn class에 직접 접근할 수 있습니다.

출력 결과 foo2 메서드에서 설정한 20을 확인할 수 있습니다.

중첩 클래스(neted class)는 내부 클래스를 직접 참조하거나, 간접적으로 참조할 수 있는 구조로 정의가 가능합니다.

Outer 클래스의 인스턴스를 먼저 만들어서 Inner 클래스를 접근하게 되면 모든 Outer 클래스를 멤버를 참조할 수 있어 하나의 인스턴스처럼 사용이 가능합니다.

감사합니다.

 

 

반응형
반응형

코틀린에서는 선언된 모든 하위 클래스를 확인할 수 있는 봉인 클래스(Sealed Class)가 있습니다.

봉인 클래스를 사용하면 상속 계층을 보호하면서 클래스를 정의할 수 있습니다.

봉인 클래스(Sealed Class)는 코틀린 1.1 이전 버전에서는 봉인 클래스를 내부에 정의했지만 1.1 버전부터는

봉인 클래스가 있는 파일 안에서 하위 클래스를 정의할 수 있게 되었습니다.

봉인 클래스가 하위에 정의되기 때문에 모든 하위 클래스를 한눈에 확인할 수 있습니다.

 

봉인 클래스(Sealed Class)

sealed class Mammal(val name:String)
class Cat(val catName: String) : Mammal(catName)
class Dog(val dogName:String) : Mammal(dogName)
class Human(val humanName:String, val job: String) : Mammal(humanName)

봉인 클래스(Sealed Class)를 선언하기 위해서는 sealed 한정자를 사용해서 선언하면 됩니다.

하위 클래스는 Mammal을 상속받아 접근이 가능합니다.

즉, 하위 클래스만 확장이 가능하고 개방이 안될 경우 다른 파일에서 사용이 불가능합니다.

개방은 조금 아래에서 확인해보겠습니다.

    fun greetMammal( mammal: Mammal) : String{
        when(mammal){
            is Cat -> return "안녕 고양이 ${mammal.name}"
            is Dog -> return "안녕 강아지 ${mammal.name}"
            is Human -> return "안녕 ${mammal.name} == 직업은 ${mammal.job}"
        }
    }
    
    Log.d(TAG,"-------- Sealed Classes ---------")

    Log.d(TAG, greetMammal(Cat("야옹이")))
        
    Log.d(TAG,"-------- Sealed END ---------")

greetMammal 함수를 사용해서 선언한 클래스를 when 제어문을 사용해서 확인할 수 있습니다.

Logcat View에서 봉인 클래스(Sealed Class) 출력 정보를 확인할 수 있습니다.

sealed class Mammal(val name:String)
class Cat(val catName: String) : Mammal(catName)
class Dog(val dogName:String) : Mammal(dogName)
class Human(val humanName:String, val job: String) : Mammal(humanName)

class Dog2: Cat("ddd")

봉인 클래스(Sealed Class)를 상속받은 클래스를 일반 클래스에서 상속받게 되면 봉인되어 있어 상속이 불가능합니다.

class Dog2에서 Cat class를 상속받으면 에러가 발생하는 것을 확인할 수 있습니다.

sealed class Mammal(val name:String)
open class Cat(val catName: String) : Mammal(catName)
class Dog(val dogName:String) : Mammal(dogName)
class Human(val humanName:String, val job: String) : Mammal(humanName)

class Dog2: Cat("ddd")

코틀린(Kotlin) 봉인 클래스(Sealed Class)를 다른 파일에서 사용하기 위해서는 open 한정자를 사용해서 개방을 해야 합니다.

Cat class를 개방하면 오류 없이 정상적으로 컴파일됩니다.

 

봉인 클래스(Sealed Class) 적용 사례

코틀린(Kotlin) 봉인 클래스(Sealed Class)는 상속 계층을 보호하는 기능이 가장 큰 특징이지만

링크 리스트나 이진트리 같은 데이터 형식 정의에 적용하면 데이터 상속에 따른 오류를 최소화할 수 있습니다.

또한 특정 클래스를 확장하지 못하게 하면서 특정 모듈에 API를 보호하면서 개발할 수 있습니다.

감사합니다.

play.kotlinlang.org/byExample/03_special_classes/03_Sealed%20Classes

 

Kotlin Playground: Edit, Run, Share Kotlin Code Online

 

play.kotlinlang.org

 

반응형

+ Recent posts