Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@
<data android:pathPrefix="/recipes/" />
</intent-filter>
</activity>

<provider
android:name=".data.local.RecipeContentProvider"
android:authorities="com.survivalcoding.gangnam2kiandroidstudy.provider"
android:exported="true" />
</application>

</manifest>
Original file line number Diff line number Diff line change
@@ -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<out String>?,
selection: String?,
selectionArgs: Array<out String>?,
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<out String>?): Int = 0

override fun update(
uri: Uri,
values: ContentValues?,
selection: String?,
selectionArgs: Array<out String>?
): Int = 0
}
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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<List<RecipeEntity>>

Expand Down
64 changes: 64 additions & 0 deletions docs/ContentProvider/RecipeContentProvider 구현 상세.md
Original file line number Diff line number Diff line change
@@ -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
<provider
android:name=".data.local.RecipeContentProvider"
android:authorities="com.survivalcoding.gangnam2kiandroidstudy.provider"
android:exported="true" />
```

## 제공 데이터 컬럼 명세
외부 앱이 `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 형식 문자열) |
Loading