ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • TECHIT 앱 스쿨 2기: Android 53일차 (23.07.13)
    [THEC!T] 앱 스쿨2기 : Android 2023. 7. 14. 00:43
    728x90

    자료 출처 : 안드로이드 앱스쿨 2기 윤재성 강사님 수업 내용

     

    오늘은 안드로이드 4대 구성요소 중 하나인 Content Provider에 대한 설명으로 강의를 시작하셨습니다.

     

    Content Provider

    간단하게 설명하자면 어플리케이션이 어플1(제공자)어플2(요청자) 가 있다고 가정하면, 어플2에서 어플1 내부에서 설정한 방법에 따라 어플1의 데이터를 사용할 수 있습니다. 즉, 어플1이 설정하지 않은 방법은 어플2에서는 사용이 불가능합니다.

    만약 가능하다면 어플2에서 어플1의 내부 데이터를 건드려 무결성을 깨뜨릴 수 도 있기 때문입니다.

     

    생성방법

     

    이 때, URI Authorities 에는 다른 어플에서 해당 링크를 통해서 접근할 수 있도록 지정해야 합니다.

    구글에서 권장하는 방식은 하기와 같습니다.

    콘텐츠 URI

    콘텐츠 URI는 제공자에서 데이터를 식별하는 URI입니다. 콘텐츠 URI에는 전체 제공자의 상징적인 이름(제공자의 권한)과 테이블을 가리키는 이름(경로)이 포함됩니다. 제공자 내의 테이블에 액세스하기 위해 클라이언트 메서드를 호출하는 경우, 테이블의 콘텐츠 URI는 인수 중 하나가 됩니다.

    앞의 코드에는 상수 CONTENT_URI에 사용자 사전 '단어' 테이블의 콘텐츠 URI가 포함되어 있습니다. ContentResolver 객체가 이 URI의 권한을 파싱한 다음, 이를 이용해 제공자를 '확인'합니다. 즉 이 권한을 알려진 제공자로 이루어진 시스템 테이블과 비교하는 것입니다. 그러면 ContentResolver가 쿼리 인수를 올바른 제공자에게 발송할 수 있습니다.

    ContentProvider는 콘텐츠 URI의 경로 부분을 사용하여 액세스할 테이블을 선택합니다. 일반적으로 제공자에는 제공자가 노출하는 테이블마다 경로가 있습니다.

     

    원본 링크 : https://developer.android.com/guide/topics/providers/content-provider-basics?hl=ko#kotlin

     

    콘텐츠 제공자 기본 사항  |  Android 개발자  |  Android Developers

    콘텐츠 제공자 기본 사항 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 콘텐츠 제공자는 중앙 저장소로의 데이터 액세스를 관리합니다. 제공자는 Android 애

    developer.android.com

     

    상기와 같이 생성을 지정하여 생성을 하면 하기와 같이 나옵니다.

     

    package com.test.android75_contentproviderapp1
    
    import android.content.ContentProvider
    import android.content.ContentValues
    import android.database.Cursor
    import android.net.Uri
    
    class SampleContentProvider : ContentProvider() {
    
        override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int {
            TODO("Implement this to handle requests to delete one or more rows")
        }
    
        override fun getType(uri: Uri): String? {
            TODO(
                "Implement this to handle requests for the MIME type of the data" +
                        "at the given URI"
            )
        }
    
        override fun insert(uri: Uri, values: ContentValues?): Uri? {
            TODO("Implement this to handle requests to insert a new row.")
        }
    
        override fun onCreate(): Boolean {
            TODO("Implement this to initialize your content provider on startup.")
        }
    
        override fun query(
            uri: Uri, projection: Array<String>?, selection: String?,
            selectionArgs: Array<String>?, sortOrder: String?
        ): Cursor? {
            TODO("Implement this to handle query requests from clients.")
        }
    
        override fun update(
            uri: Uri, values: ContentValues?, selection: String?,
            selectionArgs: Array<String>?
        ): Int {
            TODO("Implement this to handle requests to update one or more rows.")
        }
    }

    각각 함수에 대하여 설명하자면 하기와 같습니다. 

    (자료 제공 어플 내 DBHelper Class)

    class DBHelper(context:Context) : SQLiteOpenHelper(context, "Test.db", null, 1) {
        override fun onCreate(p0: SQLiteDatabase?) {
            val sql = """
                create table TestTable(
                    idx integer primary key autoincrement,
                    textData text not null,
                    intData integer not null,
                    doubleData real not null,
                    dateData date not null
                )
            """.trimIndent()
    
            p0?.execSQL(sql)
        }
    
        override fun onUpgrade(p0: SQLiteDatabase?, p1: Int, p2: Int) {
            TODO("Not yet implemented")
        }
    }
    lateinit var sqliteDatabase:SQLiteDatabase
    
    // delete
    // 두 번째 : 조건절
    // 세 번째 : 조건절의 ?에 설정될 값 배열
    override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int {
        val cnt = sqliteDatabase.delete("TestTable", selection, selectionArgs)
        return cnt
    }
    
    // 컬럼의 데이터 타입을 MIME 타입 형태로 문자열을 만들어 반환하는 메서드
    // 알려줄 필요가 없다면 null을 반환한다.
    override fun getType(uri: Uri): String? {
        return null
    }
    
    // insert
    // 두 번째 매개변수 : 저장할 데이터의 컬럼의 이름과 값이 담긴 객체
    override fun insert(uri: Uri, values: ContentValues?): Uri? {
        // 저장한다.
        sqliteDatabase.insert("TestTable", null, values)
    
        return uri;
    }
    
    // Content Provider 객체가 생성되면 자동으로 호출되는 메서드
    // 데이터 베이스에 접근할 수 있는 객체를 생성하고
    // 접속에 성공하면 true, 실패하면 false를 반환하도록 구현해준다.
    override fun onCreate(): Boolean {
        val dbHelper = DBHelper(context!!)
        sqliteDatabase = dbHelper.writableDatabase
        
        // 접속에 실패하면 false를 반환한다
        if(sqliteDatabase == null){
            return false
        }
        
        return true
    }
    
    // select
    // 두 번째 : 가져올 컬럼 목록
    // 세 번째 : 조건절
    // 네 번째 : 조건절 ?에 설정될 값
    // 다 번째 : 정렬 기준
    override fun query(
        uri: Uri, projection: Array<String>?, selection: String?,
        selectionArgs: Array<String>?, sortOrder: String?
    ): Cursor? {
        val cursor = sqliteDatabase.query("TestTable", projection, selection, selectionArgs, null, null, sortOrder)
        return cursor
    }
    
    // 두 번째 : 변경할 컬럼의 이름과 값이 있는 ContentValues 객체
    // 세 번째 : 조건절
    // 네 번째 : 조건절의 ? 에 설정될 값들의 배열
    override fun update(
        uri: Uri, values: ContentValues?, selection: String?,
        selectionArgs: Array<String>?
    ): Int {
        val cnt = sqliteDatabase.update("TestTable", values, selection, selectionArgs)
        return cnt
    }

     

    상기에는 적지 않았지만 만약 파일을 전달해야할 경우에는 getStreamTypes 함수를 오버라이딩 하여 작성 합니다.

    override fun getStreamTypes(uri: Uri, mimeTypeFilter: String): Array<String>? {
        return super.getStreamTypes(uri, mimeTypeFilter)
    }

     

    공식문서 링크

    https://developer.android.com/guide/topics/providers/content-provider-creating?hl=ko

     

    콘텐츠 제공자 생성  |  Android 개발자  |  Android Developers

    콘텐츠 제공자 생성 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 콘텐츠 제공자는 중앙 리포지토리로의 데이터 액세스를 관리합니다. Android 애플리케이

    developer.android.com

     

     

    콘텐츠 사용자 입장

     

    AndroidManifest 파일 내 권한 설정을 해야합니다.

     

    <!-- 다른 애플리케이션이 가지고 있는 Content Provider를 사용하기 위한 권한 -->
    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"
        tools:ignore="QueryAllPackagesPermission"/>

     

    사용 방법은 하기와 같습니다.

    activityMainBinding = ActivityMainBinding.inflate(layoutInflater)
    
            activityMainBinding.run {
    
                buttonInsertData.run{
                    setOnClickListener {
    
                        val sdf = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
                        val now = sdf.format(Date())
    
                        // 저장할 데이터를 ContentValues에 담아 준다.
                        val cv1 = ContentValues()
                        cv1.put("textData", "문자열1")
                        cv1.put("intData", 100)
                        cv1.put("doubleData", 11.11)
                        cv1.put("dateData", now)
    
                        val cv2 = ContentValues()
                        cv2.put("textData", "문자열2")
                        cv2.put("intData", 200)
                        cv2.put("doubleData", 22.22)
                        cv2.put("dateData", now)
    
                        // Content Provider의 이름을 가지고 있는 Uri 객체를 생성한다.
                        val uri = Uri.parse("content://com.tistory.want-kotlin-pro")
                        
                        // Content Provider를 이용할 수 있는 객체를 통해 사용한다.
                        contentResolver.insert(uri, cv1)
                        contentResolver.insert(uri, cv2)
                        
                        textViewResult.text = "저장 완료"
                    }
                }
    
                buttonSelectData.run{
                    setOnClickListener {
    
                        val uri = Uri.parse("content://com.tistory.want-kotlin-pro")
                        // Content Provider를 통해서 데이터를 가져온다.
    
                        // 두 번째 : 가져올 컬럼 목록. null을 설정하면 모든 컬럼
                        // 세 번째 : 조건절
                        // 네 번째 : 조건절의 ?에 설정된 값 배열
                        // 다섯 번째 : 정렬 기준 컬럼 목록
                        val cursor = contentResolver.query(uri, null, null, null, null)
    
                        textViewResult.text = ""
    
                        while(cursor?.moveToNext()!!){
                            val idx1 = cursor?.getColumnIndex("idx")
                            val idx2 = cursor?.getColumnIndex("textData")
                            val idx3 = cursor?.getColumnIndex("intData")
                            val idx4 = cursor?.getColumnIndex("doubleData")
                            val idx5 = cursor?.getColumnIndex("dateData")
    
                            val idx = cursor.getInt(idx1!!)
                            val textData = cursor.getString(idx2!!)
                            val intData = cursor.getInt(idx3!!)
                            val doubleData = cursor.getDouble(idx4!!)
                            val dateData = cursor.getString(idx5!!)
    
                            textViewResult.append("idx : ${idx}\n")
                            textViewResult.append("textData : ${textData}\n")
                            textViewResult.append("intData : ${intData}\n")
                            textViewResult.append("doubleData : ${doubleData}\n")
                            textViewResult.append("dateData : ${dateData}\n\n")
                        }
                    }
                }
                
                buttonUpdateData.run{
                    setOnClickListener { 
                        
                        val cv1 = ContentValues()
                        cv1.put("textData", "새로운 문자열")
                        
                        val where = "idx = ?"
                        val args = arrayOf("1")
    
                        val uri = Uri.parse("content://com.tistory.want-kotlin-pro")
                        
                        // 수정한다.
                        contentResolver.update(uri, cv1, where, args)
                        
                        textViewResult.text = "수정 완료"
                    }
                }
    
                buttonDeleteData.run{
                    setOnClickListener {
    
                        val where = "idx = ?"
                        val args = arrayOf("1")
    
                        val uri = Uri.parse("content://com.tistory.want-kotlin-pro")
    
                        contentResolver.delete(uri, where, args)
    
                        textViewResult.text = "삭제 완료"
                    }
                }
            }
    
            setContentView(activityMainBinding.root)
        }

     

    이후로 Preferences 와 리소스와 이미지 애니메이션을 알려주셨습니다.

    해당 내용은 좀 더 복습을 한 후 추가로 작성 할 수 있도록 하겠습니다.

     

    마무리

    교육 진행 중 매터리얼3 디자인이 새롭게 나와 시간이 될 때 해당 디자인을 적용시키는 방법을 확인 해봐야 할 것 같습니다.

     

    https://m3.material.io/foundations

     

    Foundations — Material Design 3

    Foundations inform the basis of any great user interface, from accessibility standards to essential patterns for layout and interaction.

    m3.material.io

    흐음.... 점점 배울게 많아지는 계절인것 같습니다.  역시 배움은 끝이 없는 것 같습닏 ...ㅠㅜ 지금 있던 것도 겨우 따라가는 것 같은데 말이죠.....

     

    오늘의 마음가짐 

    에라 모르겠다 계속 공부해야지......

Designed by Tistory.