From a446f0288de37da7a10551091bb931c14076f43d Mon Sep 17 00:00:00 2001 From: Neouul Date: Thu, 8 Jan 2026 17:16:43 +0900 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20=EB=B6=81=EB=A7=88=ED=81=AC?= =?UTF-8?q?=EB=90=9C=20=EB=A0=88=EC=8B=9C=ED=94=BC=EB=A5=BC=20=EC=A0=9C?= =?UTF-8?q?=EA=B3=B5=ED=95=98=EB=8A=94=20ContentProvider=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - RecipeContentProvider를 구현하여 북마크된 레시피 목록을 외부 앱에 노출 - RecipeDao에 Cursor를 반환하는 `getBookmarkedRecipesCursor()` 메서드 추가 - AndroidManifest.xml에 ContentProvider 등록 --- app/src/main/AndroidManifest.xml | 5 ++ .../data/local/RecipeContentProvider.kt | 58 +++++++++++++++++++ .../data/local/RecipeDao.kt | 4 ++ 3 files changed, 67 insertions(+) create mode 100644 app/src/main/java/com/survivalcoding/gangnam2kiandroidstudy/data/local/RecipeContentProvider.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 12f8a8bb3..e98558f38 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -58,6 +58,11 @@ + + \ No newline at end of file diff --git a/app/src/main/java/com/survivalcoding/gangnam2kiandroidstudy/data/local/RecipeContentProvider.kt b/app/src/main/java/com/survivalcoding/gangnam2kiandroidstudy/data/local/RecipeContentProvider.kt new file mode 100644 index 000000000..a7c88c677 --- /dev/null +++ b/app/src/main/java/com/survivalcoding/gangnam2kiandroidstudy/data/local/RecipeContentProvider.kt @@ -0,0 +1,58 @@ +package com.survivalcoding.gangnam2kiandroidstudy.data.local + +import android.content.ContentProvider +import android.content.ContentValues +import android.content.UriMatcher +import android.database.Cursor +import android.net.Uri +import org.koin.android.ext.android.inject + +class RecipeContentProvider : ContentProvider() { + + private val recipeDao: RecipeDao by inject() + + companion object { + private const val AUTHORITY = "com.survivalcoding.gangnam2kiandroidstudy.provider" + private const val BOOKMARKED_RECIPES = 1 + private val uriMatcher = UriMatcher(UriMatcher.NO_MATCH).apply { + addURI(AUTHORITY, "bookmarked_recipes", BOOKMARKED_RECIPES) + } + } + + override fun onCreate(): Boolean = true + + override fun query( + uri: Uri, + projection: Array?, + selection: String?, + selectionArgs: Array?, + sortOrder: String? + ): Cursor? { + return when (uriMatcher.match(uri)) { + BOOKMARKED_RECIPES -> { + recipeDao.getBookmarkedRecipesCursor().apply { + setNotificationUri(context?.contentResolver, uri) + } + } + else -> null + } + } + + override fun getType(uri: Uri): String? { + return when (uriMatcher.match(uri)) { + BOOKMARKED_RECIPES -> "vnd.android.cursor.dir/vnd.$AUTHORITY.bookmarked_recipes" + else -> null + } + } + + override fun insert(uri: Uri, values: ContentValues?): Uri? = null + + override fun delete(uri: Uri, selection: String?, selectionArgs: Array?): Int = 0 + + override fun update( + uri: Uri, + values: ContentValues?, + selection: String?, + selectionArgs: Array? + ): Int = 0 +} diff --git a/app/src/main/java/com/survivalcoding/gangnam2kiandroidstudy/data/local/RecipeDao.kt b/app/src/main/java/com/survivalcoding/gangnam2kiandroidstudy/data/local/RecipeDao.kt index b816063ab..b8a5c1729 100644 --- a/app/src/main/java/com/survivalcoding/gangnam2kiandroidstudy/data/local/RecipeDao.kt +++ b/app/src/main/java/com/survivalcoding/gangnam2kiandroidstudy/data/local/RecipeDao.kt @@ -1,5 +1,6 @@ package com.survivalcoding.gangnam2kiandroidstudy.data.local +import android.database.Cursor import androidx.room.Dao import androidx.room.Insert import androidx.room.OnConflictStrategy @@ -8,6 +9,9 @@ import kotlinx.coroutines.flow.Flow @Dao interface RecipeDao { + @Query("SELECT * FROM recipes WHERE id IN (SELECT recipeId FROM bookmarks)") + fun getBookmarkedRecipesCursor(): Cursor + @Query("SELECT * FROM recipes") fun getAllRecipes(): Flow> From e0ebc3373f7433e295f4e0e36aedf30fca885444 Mon Sep 17 00:00:00 2001 From: Neouul Date: Thu, 8 Jan 2026 17:17:16 +0900 Subject: [PATCH 2/2] =?UTF-8?q?docs:=20RecipeContentProvider=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20=EC=83=81=EC=84=B8=20=EB=AC=B8=EC=84=9C=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 컨텐츠 URI 패턴 설계 - MIME 타입 정의 - AndroidManifest 설정 방법 - 제공 데이터의 컬럼 명세 --- ...4\355\230\204 \354\203\201\354\204\270.md" | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 "docs/ContentProvider/RecipeContentProvider \352\265\254\355\230\204 \354\203\201\354\204\270.md" diff --git "a/docs/ContentProvider/RecipeContentProvider \352\265\254\355\230\204 \354\203\201\354\204\270.md" "b/docs/ContentProvider/RecipeContentProvider \352\265\254\355\230\204 \354\203\201\354\204\270.md" new file mode 100644 index 000000000..e2cd7af0a --- /dev/null +++ "b/docs/ContentProvider/RecipeContentProvider \352\265\254\355\230\204 \354\203\201\354\204\270.md" @@ -0,0 +1,64 @@ +# RecipeContentProvider 구현 상세 + +프로젝트의 북마크된 레시피 데이터를 외부 앱과 공유하기 위해 구현된 `RecipeContentProvider`의 설계 및 설정 내용입니다. + +## 1. 컨텐츠 URI 패턴 설계 +외부 앱이 데이터에 접근하기 위한 주소를 정의합니다. + +- **Authority**: `com.survivalcoding.gangnam2kiandroidstudy.provider` (프로젝트 고유 식별자) +- **Path**: `bookmarked_recipes` (북마크된 레시피 목록 테이블 접근) +- **Full URI**: `content://com.survivalcoding.gangnam2kiandroidstudy.provider/bookmarked_recipes` + +`RecipeContentProvider.kt`의 `UriMatcher`를 통해 위 패턴을 매칭하도록 설계되었습니다. +```kotlin +private const val AUTHORITY = "com.survivalcoding.gangnam2kiandroidstudy.provider" +private const val BOOKMARKED_RECIPES = 1 +private val uriMatcher = UriMatcher(UriMatcher.NO_MATCH).apply { + addURI(AUTHORITY, "bookmarked_recipes", BOOKMARKED_RECIPES) +} +``` + +## 2. MIME 타입 설계 +조회된 데이터의 형식을 시스템에 알려주기 위해 MIME 타입을 정의합니다. 목록(`dir`) 형태를 반환하므로 다음과 같이 설계했습니다. + +- **MIME Type**: `vnd.android.cursor.dir/vnd.com.survivalcoding.gangnam2kiandroidstudy.provider.bookmarked_recipes` + +`RecipeContentProvider.kt`의 `getType` 메서드에서 반환됩니다. +```kotlin +override fun getType(uri: Uri): String? { + return when (uriMatcher.match(uri)) { + BOOKMARKED_RECIPES -> "vnd.android.cursor.dir/vnd.$AUTHORITY.bookmarked_recipes" + else -> null + } +} +``` + +## 3. AndroidManifest 설정 +시스템이 `ContentProvider`를 인식하고 외부 앱이 접근할 수 있도록 매니페스트에 등록했습니다. + +- **File**: `app/src/main/AndroidManifest.xml` +- **Settings**: + - `android:name`: 프로바이더 클래스 경로 + - `android:authorities`: 위에서 설계한 Authority 값 + - `android:exported="true"`: **중요!** 다른 앱에서 이 프로바이더에 접근할 수 있도록 허용 + +```xml + +``` + +## 제공 데이터 컬럼 명세 +외부 앱이 `Cursor`를 통해 읽어갈 수 있는 컬럼 목록입니다. (Room `RecipeEntity` 기반) + +| Column Name | Type | Description | +| :--- | :--- | :--- | +| `id` | Long | 레시피 고유 식별자 (PK) | +| `name` | String | 레시피 명칭 | +| `category` | String | 레시피 카테고리 | +| `image` | String | 음식 이미지 URL | +| `chef` | String | 제작 셰프 명 | +| `time` | String | 조리 소요 시간 | +| `rating` | Double | 레시피 평점 | +| `ingredientsJson` | String | 재료 목록 (JSON 형식 문자열) |