타닥타닥 개발자의 일상

Kotlin 사진찍은 장소 위치 메모와 함께 저장하는 Android 앱 만들기 / RecyclerView, Camera, SQLite, Singleton 사용 본문

코딩 기록/Kotlin

Kotlin 사진찍은 장소 위치 메모와 함께 저장하는 Android 앱 만들기 / RecyclerView, Camera, SQLite, Singleton 사용

NomadHaven 2022. 3. 3. 19:41
폴더 및 파일구성

코딩 하기 전 AndroidManifest.xml / build.gradle 파일을 수정해 사전 설정을 완료해야한다.

 


사전설정 

AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.example.mydocument">


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

    <!--geo-->
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.INTERNET"/>


    <application
            android:allowBackup="true"
            android:icon="@mipmap/ic_launcher"
            android:label="@string/app_name"
            android:roundIcon="@mipmap/ic_launcher_round"
            android:supportsRtl="true"
            android:theme="@style/Theme.MyDocument"
            android:requestLegacyExternalStorage="true">
        <activity
                android:name=".SaveActivity"
                android:exported="true"/>
        <activity
                android:name=".MainActivity"
                android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>

                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
    </application>

</manifest>
build.gradle(:app)
plugins {
    id 'com.android.application'
    id 'kotlin-android'
}

android {
    compileSdk 32

    defaultConfig {
        applicationId "com.example.mydocument"
        minSdk 21
        targetSdk 32
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = '1.8'
    }
}

dependencies {
    implementation 'com.google.android.gms:play-services-location:17.0.0'
    implementation 'com.google.android.gms:play-services-maps:17.0.0'

    implementation 'androidx.recyclerview:recyclerview:1.1.0'           // 추가
    implementation 'com.github.bumptech.glide:glide:4.11.0'             // 추가
    annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'     // 추가


    implementation 'androidx.core:core-ktx:1.7.0'
    implementation 'androidx.appcompat:appcompat:1.4.1'
    implementation 'com.google.android.material:material:1.5.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
    implementation 'com.google.android.gms:play-services-location:19.0.1'
    testImplementation 'junit:junit:4.+'
    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}

 


activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">
    <androidx.recyclerview.widget.RecyclerView
            android:layout_width="503dp"
            android:layout_height="633dp"
            app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            android:layout_marginBottom="48dp" app:layout_constraintHorizontal_bias="0.504"
            android:id="@+id/recyclerView"/>
    <Button
            android:text="추가"
            android:layout_width="298dp"
            android:layout_height="58dp" android:id="@+id/mainAddBtn"
            app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toTopOf="@+id/recyclerView"
            app:layout_constraintVertical_bias="0.692"/>
</androidx.constraintlayout.widget.ConstraintLayout>

디자인 화면

*Activity 생성은  example 폴더 우클릭 > 새로만들기 > Activity > Empty Activity로 생성

MainActivity.kt
package com.example.mydocument

import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView

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


        val mainAddBtn = findViewById<Button>(R.id.mainAddBtn)

        mainAddBtn.setOnClickListener {
            val intent = Intent(this, SaveActivity::class.java)
            startActivity(intent)
        }

        val dbHelper = DBHelper.getInstance(this,"localInfo.db",)
        val list = dbHelper.printList()
        // 확인!
        for (i in list.indices){
            println(list[i].toString())
        }


        val recyclerView = findViewById<RecyclerView>(R.id.recyclerView)
        val mAdapter = CustomAdapter(this,list)
        recyclerView.adapter = mAdapter

        val layout = LinearLayoutManager(this)
        recyclerView.layoutManager=layout //recycler 뷰에 리니어 레이아웃 적용
        recyclerView.setHasFixedSize(true)




    }
}

view.item_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout

        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:layout_margin="5dp">

    <TextView
            android:id="@+id/RcLocation"
            android:text="주소"
            android:layout_width="259dp"
            android:layout_height="45dp"
            android:textSize="15sp"
            android:textStyle="bold"

            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent" app:layout_constraintHorizontal_bias="0.377"
            app:layout_constraintVertical_bias="0.533"/>
    <TextView
            android:id="@+id/RcDate"
            android:text="일자"
            android:layout_width="134dp"
            android:layout_height="19dp"
            android:textSize="10sp"
            android:textStyle="bold"

            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintVertical_bias="0.2" app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent" app:layout_constraintHorizontal_bias="1.0"/>
    <TextView
            android:id="@+id/rcContent"
            android:text="메모"
            android:layout_width="133dp"
            android:layout_height="20dp"
            android:textSize="10sp"
            android:textStyle="bold"

            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintVertical_bias="0.825" app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent" app:layout_constraintHorizontal_bias="1.0"/>
    <TextView
            android:id="@+id/RcSeq"
            android:text="번호"
            android:layout_width="34dp"
            android:layout_height="24dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.012"/>
    <ImageView
            android:layout_width="52dp"
            android:layout_height="45dp" tools:srcCompat="@tools:sample/avatars" android:id="@+id/RcimageView"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.096"
            app:layout_constraintVertical_bias="0.533"/>

</androidx.constraintlayout.widget.ConstraintLayout>

디자인화면

CustomAdapter.kt
package com.example.mydocument
import android.content.Context
import android.graphics.BitmapFactory
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import java.io.File


class CustomAdapter(private val context:Context, private val dataList: ArrayList<SaveInfoData>)
    :RecyclerView.Adapter<CustomAdapter.ItemViewHolder>()
{
    //RecyclerView에 binding 해줄 Adapter를 연결시킨다.

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

    }

    override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
        holder.bind(dataList[position], context)
        //recycleView에 있는 각각의 아이템과 아이템의 위치
    }

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


 inner class ItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView){
    private val RcSeq = itemView.findViewById<TextView>(R.id.RcSeq)
    private val RcimageView = itemView.findViewById<ImageView>(R.id.RcimageView)
    private val RcLocation = itemView.findViewById<TextView>(R.id.RcLocation)
    private val RcDate = itemView.findViewById<TextView>(R.id.RcDate)
    private val RcContent = itemView.findViewById<TextView>(R.id.rcContent)


    //data -> resource (binding)
    fun bind(dataVo: SaveInfoData,context: Context){

        //사진 처리
        if (dataVo.imagePath != "") {
            val resourceId =
                context.resources.getIdentifier(dataVo.imagePath, "drawable", context.packageName)

            if (resourceId > 0) {
                RcimageView.setImageResource(resourceId)
            } else {
                // userPhoto.setImageResource(R.mipmap.ic_launcher_round)
                // Glide.with(itemView).load(dataVo.photo).into(userPhoto)
                Log.d("","~~~~~~~~~~~~~~~~~~~~~ 들어옴")

                val file: File = File(dataVo.imagePath)
                val bExist = file.exists()
                if (bExist) {
                    Log.d("","이미지 파일 있음")
                    val myBitmap = BitmapFactory.decodeFile(dataVo.imagePath)
                    RcimageView.setImageBitmap(myBitmap)
                }else{
                    Log.d("","${dataVo.imagePath} 이미지 파일 없음")
                }
            }
        } else {
            RcimageView.setImageResource(R.mipmap.ic_launcher_round)
        }

        //TextView 데이터 세팅 작업
        RcSeq.text = dataVo.seq.toString()
        RcLocation.text =dataVo.location
        RcDate.text = dataVo.date
        RcContent.text =dataVo.content

        // 클릭시에
        itemView.setOnClickListener {
            println(dataVo.content)

            /*Intent(context, ProfileDetailActivity::class.java).apply {
                putExtra("data", dataVo)
                addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) // 새로운 태스크를 생성하여 그 태스크안에 엑티비티를 추가하게 됩니다.
            }.run { context.startActivity(this) }*/
        }
    }


    }

}

DocumentDto.kt
package com.example.mydocument

data class DocumentDto(var seq:Int, var imagePath:String, var location:String, var content:String, var date:String){
    override fun toString(): String {
        return "$seq $imagePath $location $content $date"
    }
}
DBHelper.kt
package com.example.mydocument

import android.content.Context
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper

class DBHelper(context: Context, filename:String): SQLiteOpenHelper(context,filename,null,1) {

    companion object {
        var dbhelper: DBHelper? = null
        fun getInstance(context: Context, filename: String): DBHelper {
            if (dbhelper == null) {
                dbhelper = DBHelper(context, filename)
            }
            return dbhelper!!
        }
    }

    override fun onCreate(db: SQLiteDatabase?) {
        var sql: String = "CREATE TABLE IF NOT EXISTS localInfo( " +
                "SEQ INTEGER PRIMARY KEY AUTOINCREMENT, " +
                "ImagePath STRING, " +
                "Location STRING," +
                "Content STRING, " +
                "Date TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL)"

        db?.execSQL(sql)
    }


    override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
        val sql: String = "DROP TABLE if exists localInfo"
        db?.execSQL(sql)
        onCreate(db)

    }

    fun insert(dto: SaveInfoData){
        println("데이터 입력 완료")

        var sql = " INSERT INTO localInfo(ImagePath,Location,Content) " +
                " VALUES('${dto.imagePath}','${dto.location}','${dto.content}')"

        var db = this.writableDatabase
        db.execSQL(sql)

        // 확인용
        var query = "SELECT * FROM localInfo"
        var cursor = db.rawQuery(query,null)
        while(cursor.moveToNext()){
            println(cursor.getInt(cursor.getColumnIndex("SEQ")).toString() + " : "
                    + cursor.getString(cursor.getColumnIndex("ImagePath")) + " : "
                    + cursor.getString(cursor.getColumnIndex("Location")) + " : "
                    + cursor.getString(cursor.getColumnIndex("Content")) + " : "
                    + cursor.getString(cursor.getColumnIndex("Date"))
            )
        }

    }

    fun printList(): ArrayList<SaveInfoData>{
        var list = ArrayList<SaveInfoData>()
        val query = " SELECT * FROM localInfo"

        var db = this.writableDatabase
        var cursor = db.rawQuery(query, null)


        while (cursor.moveToNext()) {
            var _seq = cursor.getColumnIndex("SEQ")
            var _imagepath = cursor.getColumnIndex("ImagePath")
            var _location = cursor.getColumnIndex("Location")
            var _content = cursor.getColumnIndex("Content")
            var _date = cursor.getColumnIndex("Date")

            val seq = cursor.getInt(_seq)
            val imagepath = cursor.getString(_imagepath)
            val location = cursor.getString(_location)
            val content = cursor.getString(_content)
            val date = cursor.getString(_date)

            println("$seq : $imagepath: $location : $content")

            list.add(SaveInfoData(seq,imagepath,location,content,date)) //모든 매개변수가 지정되지 않아 에러난다
        }

        cursor.close()

        return list
    }

}

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

    <Button
            android:text="save"
            android:layout_width="136dp"
            android:layout_height="53dp"
            android:id="@+id/saveContentBtn"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintHorizontal_bias="0.721"
            app:layout_constraintVertical_bias="0.05"/>
    <Button
            android:text="camera"
            android:layout_width="136dp"
            android:layout_height="53dp"
            android:id="@+id/saveCamBtn"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintHorizontal_bias="0.29"
            app:layout_constraintVertical_bias="0.05"/>
    <ImageView
            android:layout_width="431dp"
            android:layout_height="423dp"
            tools:srcCompat="@tools:sample/avatars"
            android:id="@+id/avatars"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintHorizontal_bias="0.55"
            app:layout_constraintVertical_bias="0.232"/>
    <EditText
            android:layout_width="414dp"
            android:layout_height="74dp"
            android:inputType="textPersonName"
            android:ems="10"
            android:id="@+id/saveEditTxtContent"
            android:hint="추가로 기입할 정보"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintVertical_bias="0.737"/>
    <TextView
            android:text="이미지 경로"
            android:layout_width="417dp"
            android:layout_height="71dp" android:id="@+id/saveTxtView"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintHorizontal_bias="0.491"
            app:layout_constraintVertical_bias="0.844"/>
</androidx.constraintlayout.widget.ConstraintLayout>

디자인화면

SaveActivity.kt
package com.example.mydocument

import android.Manifest
import android.annotation.SuppressLint
import android.app.Activity
import android.content.ContentValues
import android.content.DialogInterface
import android.content.Intent
import android.content.pm.PackageManager
import android.database.Cursor
import android.graphics.Bitmap
import android.location.Address
import android.location.Geocoder
import android.net.Uri
import android.os.Build
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.os.Looper
import android.provider.MediaStore
import android.util.Log
import android.widget.*
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import com.google.android.gms.location.*
import java.io.FileOutputStream
import java.io.IOException
import java.text.SimpleDateFormat


class SaveActivity : AppCompatActivity() {


    // storage 권한 처리에 필요한 변수
    val CAMERA = arrayOf(Manifest.permission.CAMERA)
    val STORAGE = arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE,
        Manifest.permission.WRITE_EXTERNAL_STORAGE)
    val CAMERA_CODE = 98
    val STORAGE_CODE = 99

    // 권한처리
    lateinit var locationPermission: ActivityResultLauncher<Array<String>>
    lateinit var fusedLocationClient: FusedLocationProviderClient
    lateinit var locationCallback: LocationCallback

    // 위도와 경도
    var latitude:Double = 0.0
    var longitude:Double = 0.0

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

        // 권한
        locationPermission = registerForActivityResult(
            ActivityResultContracts.RequestMultiplePermissions()) { results ->
            if (!results.all { it.value }) {
                Toast.makeText(this, "권한 승인이 필요합니다", Toast.LENGTH_LONG).show()
            }
        }

        // 권한 요청
        locationPermission.launch(
            arrayOf(
                Manifest.permission.ACCESS_COARSE_LOCATION,
                Manifest.permission.ACCESS_FINE_LOCATION
            )
        )
        fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)

        // 위치 정보를 실시간으로 받기 위한 함수를 호출
        updateLocation()


        //카메라 촬영
        val saveCamBtn = findViewById<Button>(R.id.saveCamBtn)
        saveCamBtn.setOnClickListener {
            CallCamera()
        }

        // 이미지 경로
        val saveTxtView = findViewById<TextView>(R.id.saveTxtView)
        // 내용
        val saveEditTxtContent = findViewById<EditText>(R.id.saveEditTxtContent)
        //저장버튼
        val saveContentBtn = findViewById<Button>(R.id.saveContentBtn)

        saveContentBtn.setOnClickListener {
            // 이미지 경로, 현재의 위치 주소, 내용

            val address = getAddress(latitude, longitude)
            var message = "위도:$latitude 경도:$longitude \n" +
                    "이미지 경로:${saveTxtView.text} \n" + //아직 아무것도 안 뜬곳 아닌가?
                    "내용:${saveEditTxtContent.text} "

            // 우선은 출력해보자!
            AlertDialog.Builder(this@SaveActivity)
                .setTitle("대화 상자")
                .setMessage(message)
                .setCancelable(false)
                .setNeutralButton("닫기", DialogInterface.OnClickListener { dialog, which ->
                }).show()

            DBHelper.getInstance(this, "localInfo.db")
                .insert(
                    SaveInfoData(0,
                        saveTxtView.text.toString(),
                        address,
                        saveEditTxtContent.text.toString(),
                        "")
                )

            Toast.makeText(this, "저장되었습니다", Toast.LENGTH_SHORT).show()

            val intent = Intent(this, MainActivity::class.java)
            startActivity(intent)

        }

    }

    fun getAddress(lat:Double, lon:Double):String{
        val geocoder:Geocoder = Geocoder(this)
        var list: List<Address>? = null
        var address = ""
        try {
            list = geocoder.getFromLocation(
                lat,  // 위도
                lon,  // 경도
                10
            ) // 얻어올 값의 개수
        } catch (e: IOException) {
            e.printStackTrace()
            Log.e("test", "입출력 오류 - 서버에서 주소변환시 에러발생")
        }
        if (list != null) {
            if (list.isEmpty()) {
                Toast.makeText(this, "해당되는 주소 정보는 없습니다", Toast.LENGTH_LONG).show()
            } else {
                address = list[0].getAddressLine(0).toString()
            }
        }
        return address
    }



    // 카메라 권한, 저장소 권한
    // 요청 권한
    override fun onRequestPermissionsResult(requestCode: Int,
                                            permissions: Array<out String>, grantResults: IntArray) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)

        when(requestCode){
            CAMERA_CODE -> {
                for (grant in grantResults){
                    if(grant != PackageManager.PERMISSION_GRANTED){
                        Toast.makeText(this, "카메라 권한을 승인해 주세요", Toast.LENGTH_LONG).show()
                    }
                }
            }
            STORAGE_CODE -> {
                for(grant in grantResults){
                    if(grant != PackageManager.PERMISSION_GRANTED){
                        Toast.makeText(this, "저장소 권한을 승인해 주세요", Toast.LENGTH_LONG).show()
                    }
                }
            }
        }
    }

    // 다른 권한등도 확인이 가능하도록
    fun checkPermission(permissions: Array<out String>, type:Int):Boolean{
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M){
            for (permission in permissions){
                if(ContextCompat.checkSelfPermission(this, permission)
                    != PackageManager.PERMISSION_GRANTED){
                    ActivityCompat.requestPermissions(this, permissions, type)
                    return false
                }
            }
        }
        return true
    }

    // 카메라 촬영 - 권한 처리
    fun CallCamera(){
        if(checkPermission(CAMERA, CAMERA_CODE) && checkPermission(STORAGE, STORAGE_CODE)){
            val itt = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
            startActivityForResult(itt, CAMERA_CODE)
        }
    }

    // 사진 저장
    fun saveFile(fileName:String, mimeType:String, bitmap: Bitmap): Uri?{

        var CV = ContentValues()

        // MediaStore 에 파일명, mimeType 을 지정
        CV.put(MediaStore.Images.Media.DISPLAY_NAME, fileName)
        CV.put(MediaStore.Images.Media.MIME_TYPE, mimeType)

        // 안정성 검사
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q){
            CV.put(MediaStore.Images.Media.IS_PENDING, 1)
        }

        // MediaStore 에 파일을 저장
        val uri = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, CV)
        if(uri != null){
            var scriptor = contentResolver.openFileDescriptor(uri, "w")

            val fos = FileOutputStream(scriptor?.fileDescriptor)

            bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos)
            fos.close()

            if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q){
                CV.clear()
                // IS_PENDING 을 초기화
                CV.put(MediaStore.Images.Media.IS_PENDING, 0)
                contentResolver.update(uri, CV, null, null)
            }
        }
        return uri
    }

    // 결과
    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)

        val imageView = findViewById<ImageView>(R.id.avatars)
        val saveTxtView = findViewById<TextView>(R.id.saveTxtView)

        if(resultCode == Activity.RESULT_OK){
            when(requestCode){
                CAMERA_CODE -> {
                    if(data?.extras?.get("data") != null){
                        val img = data?.extras?.get("data") as Bitmap
                        val uri = saveFile(RandomFileName(), "image/jpeg", img)
                        imageView.setImageURI(uri)
                        Toast.makeText(this, "$uri", Toast.LENGTH_LONG).show()
                        println("이미지 경로: $uri")

                        // 이미지 경로를 TextView 에 저장
                        saveTxtView.text = getPath(uri)
                        println("실제 이미지 경로:" + getPath(uri))

                    }
                }
                STORAGE_CODE -> {
                    val uri = data?.data
                    imageView.setImageURI(uri)
                }
            }
        }
    }

    // 파일명을 날짜 저장
    fun RandomFileName() : String{
        val fileName = SimpleDateFormat("yyyyMMddHHmmss").format(System.currentTimeMillis())
        return fileName
    }


    // 실제 경로로 변경해 주는 함수
    // Uri -> String
    fun getPath(uri: Uri?): String {
        val projection = arrayOf<String>(MediaStore.Images.Media.DATA)
        val cursor: Cursor = managedQuery(uri, projection, null, null, null)
        startManagingCursor(cursor)
        val columnIndex: Int = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA)
        cursor.moveToFirst()
        return cursor.getString(columnIndex)
    }

    // 위치 취득 함수
    @SuppressLint("MissingPermission")
    fun updateLocation(){
        val locationRequest = LocationRequest.create()
        locationRequest.run {
            priority = LocationRequest.PRIORITY_HIGH_ACCURACY
            interval = 1000
        }

        // 실시간으로 자신의 위치를 취득
        locationCallback = object : LocationCallback(){
            // 1초에 한번씩 변경된 위치 정보가 onLocationResult 로 전달된다
            /*override fun onLocationResult(locationResult: LocationResult?) {
                locationResult?.let {
                    for (location in it.locations){
                        Log.d( "위치정보", " - 위도:${location.latitude} 경도:${location.longitude}")
                        // 변수에 저장한다
                        latitude = location.latitude
                        longitude = location.longitude
                    }
                }
            }*/
            override fun onLocationResult(locationResult: LocationResult) {
                locationResult?.let {
                    for (location in it.locations){
                        Log.d( "위치정보", " - 위도:${location.latitude} 경도:${location.longitude}")
                        // 변수에 저장한다
                        latitude = location.latitude
                        longitude = location.longitude
                    }
                }
            }
        }

        // 권한 처리
        fusedLocationClient.requestLocationUpdates(locationRequest, locationCallback, Looper.myLooper()!!)
    }


}
SaveInfoData.kt
package com.example.mydocument

import android.os.Parcel
import android.os.Parcelable

class SaveInfoData(var seq: Int, var imagePath: String?,
                   var location: String?, var content:String?, var date:String?): Parcelable {
    constructor(parcel: Parcel) : this(
        parcel.readInt(),  //seq
        parcel.readString(), //imagepath
        parcel.readString(), //location
        parcel.readString(), //content
        parcel.readString()
    ) {
    }

    override fun writeToParcel(parcel: Parcel, flags: Int) {
        parcel.writeInt(seq)
        parcel.writeString(imagePath)
        parcel.writeString(location)
        parcel.writeString(content)
        parcel.writeString(date)
    }

    override fun describeContents(): Int {
        return 0
    }

    companion object CREATOR : Parcelable.Creator<SaveInfoData> {
        override fun createFromParcel(parcel: Parcel): SaveInfoData {
            return SaveInfoData(parcel)
        }

        override fun newArray(size: Int): Array<SaveInfoData?> {
            return arrayOfNulls(size)
        }
    }

}

실행화면

메인화면, 리사이클뷰에 메모, 위치, 미리보기 사진, 날짜가 보여진다.
추가 눌렀을때 SaveActivity화면
추가 누르고 사진촬영 했을때 화면

Comments