이 문서는 Kakao SDK for Android(이하 Android SDK)를 사용한 카카오스토리 기능 구현 방법을 안내합니다.
카카오스토리 API는 카카오스토리 모듈의 StoryApiClient
가 제공합니다. 또한 카카오스토리 API는 카카오 로그인이 필요한 API입니다. 모듈 설정을 참고하여 build.gradle(Module) 파일에 카카오 로그인 모듈인 v2-user
, 카카오스토리 모듈인 v2-story
를 추가합니다.
Android SDK를 통해 게시된 스토리의 링크를 통해 서비스 앱을 실행하려면 커스텀 URL 스킴 설정이 필요합니다. ${Project}
> android > app > src > AndroidManifest.xml 파일에 아래 설정을 추가합니다. Android 12(API 31) 이상을 타깃으로 하는 앱인 경우, exported
요소를 반드시 "true"로 선언해야 합니다.
<activity
android:name=".{YOUR_ACTIVITY_NAME}"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<!-- "kakao${YOUR_NATIVE_APP_KEY}://kakaostory" 형식의 앱 실행 스킴을 설정하는데 사용 -->
<data android:host="kakaostory"
android:scheme="kakao${YOUR_NATIVE_APP_KEY}" />
</intent-filter>
</activity>
권한 | 사전 설정 | 카카오 로그인 | 사용자 동의 | 레퍼런스 |
---|---|---|---|---|
- | 플랫폼 등록 카카오 로그인 활성화 동의 항목 |
필요 | - | Android SDKisStoryUser() ReactiveX Android SDK isStoryUser() |
카카오스토리 미사용으로 인한 에러를 방지하기 위해, 현재 로그인한 사용자가 카카오스토리를 사용하고 있는지 확인합니다.
StoryApiClient
의 isStoryUser()
로 현재 로그인한 사용자의 카카오스토리 사용 여부를 확인할 수 있습니다. 사용자의 카카오스토리 이용 여부는 Boolean
값으로 사용(true) 또는 미사용(false)입니다.
// 카카오스토리 사용자 확인하기
StoryApiClient.instance.isStoryUser { isStoryUser, error ->
if (error != null) {
Log.e(TAG, "카카오스토리 사용자 확인 실패", error)
}
else {
Log.i(TAG, "카카오스토리 가입 여부: $isStoryUser")
}
}
var disposables = CompositeDisposable()
// 카카오스토리 사용자 확인하기
StoryApiClient.rx.isStoryUser()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ isStoryUser ->
Log.i(TAG, "카카오스토리 가입 여부: $isStoryUser")
}, { error ->
Log.e(TAG, "카카스토리 사용자 확인 실패", error)
})
.addTo(disposables)
권한 | 사전 설정 | 카카오 로그인 | 사용자 동의 | 레퍼런스 |
---|---|---|---|---|
- | 플랫폼 등록 카카오 로그인 활성화 동의 항목 |
필요 | 필요: 프로필 정보(닉네임/프로필 사진) 닉네임 프로필 사진 카카오스토리 프로필 URL |
공통StoryProfile Android SDK profile() ReactiveX Android SDK profile() |
현재 로그인한 사용자의 카카오스토리 프로필을 불러옵니다. 사용자는 카카오계정, 카카오톡, 카카오스토리에 각각 프로필을 설정할 수 있으므로, 카카오스토리 프로필 가져오기 API로 불러온 프로필은 사용자 정보나 카카오톡 프로필 가져오기 API로 조회한 정보와 별개입니다.
사용자 카카오스토리 프로필을 불러오려면 StoryApiClient
의 profile()
API를 사용합니다.
// 카카오스토리 프로필 가져오기
StoryApiClient.instance.profile { profile, error ->
if (error != null) {
Log.e(TAG, "카카오스토리 프로필 가져오기 실패", error)
}
else if (profile != null) {
Log.i(TAG, "카카오스토리 프로필 가져오기 성공" +
"\n닉네임: ${profile.nickname}" +
"\n프로필사진: ${profile.thumbnailUrl}" +
"\n생일: ${profile.birthday}")
}
}
var disposables = CompositeDisposable()
// 카카오스토리 프로필 가져오기
StoryApiClient.rx.profile()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ profile ->
Log.i(TAG, "카카오스토리 프로필 가져오기 성공" +
"\n닉네임: ${profile.nickname}" +
"\n프로필사진: ${profile.thumbnailUrl}" +
"\n생일: ${profile.birthday}")
}, { error ->
Log.e(TAG, "카카오스토리 프로필 가져오기 실패", error)
})
.addTo(disposables)
요청 성공 시 사용자의 카카오스토리 프로필을 StoryProfile
객체로 받습니다. 카카오스토리 프로필 정보의 구성 및 자료형은 REST API 가이드 및 레퍼런스에서 확인할 수 있습니다.
현재 로그인한 사용자의 카카오스토리에 새로운 스토리를 작성합니다. 스토리 쓰기는 글(Post), 사진(Photo)과 글, 링크(Link) 세 가지 종류의 API를 제공합니다.
타입 | 설명 | 메서드 |
---|---|---|
글(Note) | 텍스트로 구성된 스토리 | postNote() |
사진(Photo) | 텍스트와 사진으로 구성된 스토리 | postPhoto() |
링크(Link) | 텍스트와 스크랩할 웹 페이지에서 얻은 정보로 구성된 스토리 | postLink() |
스토리마다 포함되어야 할 구성 요소가 다르지만, 다음 파라미터는 공통적으로 설정 가능합니다. 다음 파라미터를 통해 스토리에 포함할 내용 및 링크 정보를 설정할 수 있습니다.
이름 | 타입 | 설명 | 필수 |
---|---|---|---|
permission | Permission |
스토리 공개 범위PUBLIC (전체 공개), FRIEND (친구 공개), ONLY_ME (나만 보기)기본 값은 PUBLIC (전체 공개) |
X |
enableShare | Boolean |
친구 공개 스토리인 경우 공유 설정 (기본값: false ) |
X |
androidExecParam | Map<String, String> |
스토리의 [해당 앱으로 이동] 버튼을 눌렀을 때 Android 앱 실행 URL에 붙일 파라미터 | X |
iosExecParam | Map<String, String> |
스토리의 [해당 앱으로 이동] 버튼을 눌렀을 때 iOS 앱 실행 URL에 붙일 파라미터 | X |
androidMarketParam | Map<String, String> |
스토리에서 오픈마켓으로 이동할 때 실행 URL에 붙일 파라미터 | X |
iosMarketParam | Map<String, String> |
스토리에서 앱스토어로 이동할 때 실행 URL에 붙일 파라미터 | X |
요청 성공 시 콜백 함수로 전달되는 StoryPostResult
객체를 통해 작성된 스토리 게시물의 ID를 확인할 수 있습니다.
이름 | 타입 | 설명 |
---|---|---|
id | String |
작성된 스토리 ID |
종류별 스토리 쓰기 구현 방법과 추가 파라미터 정보는 아래에서 확인할 수 있습니다.
권한 | 사전 설정 | 카카오 로그인 | 사용자 동의 | 레퍼런스 |
---|---|---|---|---|
- | 플랫폼 등록 카카오 로그인 활성화 동의 항목 |
필요 | 필요: 카카오스토리 글 작성 (story_publish) |
Android SDKpostNote() ReactiveX Android SDK postNote() |
postNote()
API는 사진이나 웹 페이지 URL 등 다른 요소 없이 글로만 구성된 스토리를 게시할 때 사용합니다. 글 스토리 쓰기 시, 스토리 내용 텍스트를 담은 content
파라미터를 필수 전달해야 합니다.
이름 | 타입 | 설명 | 필수 |
---|---|---|---|
content | String |
스토리에 들어갈 글, 2048자(char) 미만으로 제한 | O |
다음은 글 스토리 쓰기 예제입니다. 다른 파라미터는 설정하지 않고 스토리 내용만을 content
파라미터로 전달합니다.
// 글 스토리 쓰기
val content = "Posting note from Kakao SDK Sample."
StoryApiClient.instance.postNote(content) { storyPostResult, error ->
if (error != null) {
Log.e(TAG, "스토리 쓰기 실패", error)
}
else if (storyPostResult != null) {
Log.i(TAG, "스토리 쓰기 성공 [${storyPostResult.id}]")
}
}
var disposables = CompositeDisposable()
// 글 스토리 쓰기
val content = "Posting note from Kakao SDK Sample."
StoryApiClient.rx.postNote(content)
.retryWhen(
// InsufficientScope 에러에 대해 추가 동의 후 재요청
RxAuthOperations.instance.incrementalAuthorizationRequired(context)
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ storyId ->
Log.i(TAG, "스토리 쓰기 성공 [${storyId}]")
}, { error ->
Log.e(TAG, "스토리 쓰기 실패", error)
})
.addTo(disposables)
권한 | 사전 설정 | 카카오 로그인 | 사용자 동의 | 레퍼런스 |
---|---|---|---|---|
- | 플랫폼 등록 카카오 로그인 활성화 동의 항목 |
필요 | 필요: 카카오스토리 글 작성 (story_publish) |
Android SDKpostPhoto() upload() ReactiveX Android SDK postPhoto() upload() |
postPhoto()
API는 사진과 글로 구성된 스토리를 게시할 때 사용합니다. 사진 스토리 쓰기 시, 스토리에 첨부할 이미지 정보를 담은 images
파라미터를 필수 전달해야 합니다. 스토리 내용 텍스트를 담은 content
는 선택 파라미터입니다.
images
파라미터에는 이미지 파일 URL 문자열(String)을 리스트(List) 형식으로 전달해야 합니다. 만약 기기에 저장된 이미지 파일을 사용하고 싶다면 upload()
API를 사용해 카카오 서버에 업로드합니다. 요청 시 업로드할 파일은 리스트 형식으로 전달합니다. 업로드된 이미지의 URL을 리스트 형식으로 받아 postPhoto()
파라미터로 사용할 수 있습니다.
이름 | 타입 | 설명 | 필수 |
---|---|---|---|
content | String |
스토리에 들어갈 글, 2048자(char) 미만으로 제한 | X |
images | List<String> |
스토리에 들어갈 이미지들의 URL | O |
다음은 이미지 업로드 후 사진 스토리 쓰기를 요청하는 예제입니다.
// 사진 스토리 쓰기
// 업로드할 사진 파일
// 이 예제에서는 프로젝트 리소스로 추가한 이미지 파일을 사용했습니다.
// 갤러리 등 서비스에 필요한 사진 파일을 준비합니다.
val bitmap = BitmapFactory.decodeResource(resources, R.drawable.sample1)
val file = File(context.cacheDir, "sample1.png")
val stream = FileOutputStream(file)
bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream)
stream.close()
// 사진 업로드
StoryApiClient.instance.upload(listOf(file)) { uploadedPaths, error ->
if (error != null) {
Log.e(TAG, "사진 업로드 실패", error)
} else {
Log.d(TAG, "사진 업로드 성공 $uploadedPaths")
// 사진 스토리 쓰기
val images = uploadedPaths!!
val content = "Posting photo from Kakao SDK Sample."
StoryApiClient.instance.postPhoto(images, content) { storyPostResult, error ->
if (error != null) {
Log.e(TAG, "스토리 쓰기 실패", error)
}
else if (storyPostResult != null) {
Log.i(TAG, "스토리 쓰기 성공 [${storyPostResult.id}]")
}
}
}
}
var disposables = CompositeDisposable()
// 사진 스토리 쓰기
// 업로드할 사진 파일
// 이 예제에서는 프로젝트 리소스로 추가한 이미지 파일을 사용했습니다.
// 갤러리 등 서비스에 필요한 사진 파일을 준비합니다.
val bitmap = BitmapFactory.decodeResource(resources, R.drawable.sample1)
val file = File(context.cacheDir, "sample1.png")
val stream = FileOutputStream(file)
bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream)
stream.close()
// 사진 업로드
StoryApiClient.rx.upload(listOf(file))
.flatMap {
// 사진 스토리 쓰기
val images = it
val content = "Posting photo from Kakao SDK Sample."
StoryApiClient.rx.postPhoto(images, content)
}
.retryWhen(
// InsufficientScope 에러에 대해 추가 동의 후 재요청
RxAuthOperations.instance.incrementalAuthorizationRequired(context)
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ storyId ->
Log.i(TAG, "스토리 쓰기 성공 [${storyId}]")
}, { error ->
Log.e(TAG, "스토리 쓰기 실패", error)
})
.addTo(disposables)
권한 | 사전 설정 | 카카오 로그인 | 사용자 동의 | 레퍼런스 |
---|---|---|---|---|
- | 플랫폼 등록 카카오 로그인 활성화 동의 항목 |
필요 | 필요: 카카오스토리 글 작성 (story_publish) |
공통LinkInfo Android SDK postLink() linkInfo() ReactiveX Android SDK postLink() linkInfo() |
postLink()
API는 웹 페이지를 공유하는 스토리를 게시할 때 사용합니다. 링크 스토리 쓰기 시, 공유할 웹 페이지 정보를 담은 linkInfo
파라미터를 필수 전달해야 합니다. 스토리 내용 텍스트를 담은 content
는 선택 파라미터입니다.
파라미터 값으로 전달할 LinkInfo
객체는 linkInfo()
API를 사용하여 구합니다. linkInfo()
에 공유할 웹 페이지 URL을 전달하면, 카카오 SDK가 오픈 그래프 프로토콜(Open Graph Protocol)에 따라 웹 페이지 정보를 스크랩하여 LinkInfo
객체로 반환합니다.
따라서 링크 스토리 쓰기 요청 시에는 먼저 linkInfo()
를 호출하여 공유할 웹 페이지 정보를 담은 LinkInfo
객체를 구하고, 이 요청이 성공하면 이어서 postLink()
를 호출하도록 구현하는 것을 권장합니다.
이름 | 타입 | 설명 | 필수 |
---|---|---|---|
content | String |
스토리에 들어갈 글, 2048자(char) 미만으로 제한 | X |
linkInfo | LinkInfo |
웹 페이지 스크랩 정보 | O |
다음은 웹 페이지 스크랩 후 링크 스토리 쓰기를 요청하는 예제입니다.
// 링크 스토리 쓰기
// 지정된 URL로 링크 만들기
StoryApiClient.instance.linkInfo("https://www.kakaocorp.com") { linkInfoResult, error ->
if (error != null) {
Log.e(TAG, "링크 만들기 실패", error)
} else {
Log.d(TAG, "링크 만들기 성공 $linkInfoResult")
// 링크 스토리 쓰기
val linkInfo = linkInfoResult!!
val content = "Posting link from Kakao SDK Sample."
StoryApiClient.instance.postLink(linkInfo, content) { storyPostResult, error ->
if (error != null) {
Log.e(TAG, "스토리 쓰기 실패", error)
}
else if (storyPostResult != null) {
Log.i(TAG, "스토리 쓰기 성공 [${storyPostResult.id}]")
}
}
}
}
var disposables = CompositeDisposable()
// 링크 스토리 쓰기
// 지정된 URL로 링크 만들기
StoryApiClient.rx.linkInfo("https://www.kakaocorp.com")
.flatMap {
// 링크 스토리 쓰기
val linkInfo = it
val content = "Posting link from Kakao SDK Sample."
StoryApiClient.rx.postLink(linkInfo, content)
}
.retryWhen(
// InsufficientScope 에러에 대해 추가 동의 후 재요청
RxAuthOperations.instance.incrementalAuthorizationRequired(context)
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ storyId ->
Log.i(TAG, "스토리 쓰기 성공 [${storyId}]")
}, { error ->
Log.e(TAG, "스토리 쓰기 실패", error)
})
.addTo(disposables)
현재 로그인한 사용자의 카카오스토리에서 스토리 목록을 불러옵니다.
다음 두 가지 방법으로 스토리를 가져올 수 있습니다.
스토리 가져오기 요청 시 하나의 스토리를 요청하는지, 여러 개의 스토리를 요청하는지에 따라 전달해야 할 파라미터가 다릅니다.
권한 | 사전 설정 | 카카오 로그인 | 사용자 동의 | 레퍼런스 |
---|---|---|---|---|
- | 플랫폼 등록 카카오 로그인 활성화 동의 항목 |
필요 | 필요: 카카오스토리 글 목록 (story_read) |
공통Story Android SDK stories() ReactiveX Android SDK stories() |
현재 로그인한 사용자의 카카오스토리에서 스토리 목록을 불러옵니다. stories()
로 요청하며, 요청 성공 시 각 스토리의 구성 요소를 담은 Story
객체의 리스트를 반환합니다.
각 Story
객체는 스토리 고유 번호인 ID를 포함합니다. stories()
응답은 각 스토리의 댓글이나 반응과 같은 상세 정보를 포함하지 않으므로, 각 스토리의 상세 정보를 확인하려면 해당 스토리 ID 값을 파라미터로 전달하여 stories()
를 호출해야 합니다.
// 여러 개의 스토리 가져오기
StoryApiClient.instance.stories { stories, error ->
if (error != null) {
Log.e(TAG, "스토리 가져오기 실패", error)
}
else {
Log.i(TAG, "스토리 가져오기 성공 \n$stories")
}
}
var disposables = CompositeDisposable()
// 여러 개의 스토리 가져오기
StoryApiClient.rx.stories()
.retryWhen(
// InsufficientScope 에러에 대해 추가 동의 후 재요청
RxAuthOperations.instance.incrementalAuthorizationRequired(context)
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ stories ->
Log.i(TAG, "스토리 가져오기 성공 \n$stories")
}, { error ->
Log.e(TAG, "스토리 가져오기 실패", error)
}).addTo(disposables)
권한 | 사전 설정 | 카카오 로그인 | 사용자 동의 | 레퍼런스 |
---|---|---|---|---|
- | 플랫폼 등록 카카오 로그인 활성화 동의 항목 |
필요 | 필요: 카카오스토리 글 목록 (story_read) |
공통Story Android SDK story() ReactiveX Android SDK story() |
하나의 스토리를 지정해 요청합니다.
story()
는 현재 로그인한 사용자의 카카오스토리에서 지정한 스토리의 상세 정보를 가져옵니다. 스토리 id
를 필수 전달해야 합니다. 특정 스토리 ID를 지정하여 요청하면 해당 스토리의 댓글을 포함한 상세 정보를 받을 수 있습니다.
이름 | 타입 | 설명 | 필수 |
---|---|---|---|
id | String |
상세 정보를 가져올 스토리 ID | O |
// 하나의 스토리 가져오기
// 이 예제에서는 받고자 하는 스토리 아이디를 얻기 위해 전체 목록을 조회하고 첫번째 스토리 아이디를 사용했습니다.
StoryApiClient.instance.stories { stories, error ->
if (error != null) {
Log.e(TAG, "스토리 가져오기 실패", error)
}
else if (stories != null) {
if (stories.isEmpty()) {
Log.e(TAG, "내 스토리가 하나도 없어요 ㅠㅠ")
}
else {
// 정보를 원하는 스토리 아이디
val storyId = stories.first().id
// 하나의 스토리 가져오기
StoryApiClient.instance.story(storyId) { story, error ->
if (error != null) {
Log.e(TAG, "스토리 가져오기 실패", error)
}
else if (story != null) {
Log.i(TAG, "스토리 가져오기 성공" +
"\n아이디: ${story.id}" +
"\n미디어 형식: ${story.mediaType}" +
"\n작성일자: ${story.createdAt}" +
"\n내용: ${story.content}")
}
}
}
}
var disposables = CompositeDisposable()
// 하나의 스토리 가져오기
// 이 예제에서는 받고자 하는 스토리 아이디를 얻기 위해 전체 목록을 조회하고 첫번째 스토리 아이디를 사용했습니다.
StoryApiClient.rx.stories()
.retryWhen(
// InsufficientScope 에러에 대해 추가 동의 후 재요청
RxAuthOperations.instance.incrementalAuthorizationRequired(context)
)
.flatMap { stories ->
if (stories.isNotEmpty()) {
// 정보를 원하는 스토리 아이디
val storyId = stories.first().id
StoryApiClient.rx.story(storyId)
}
else {
Single.error(ClientError(ClientErrorCause.IllegalState, "No stories"))
}
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ story ->
Log.i(TAG, "스토리 가져오기 성공" +
"\n아이디: ${story.id}" +
"\n미디어 형식: ${story.mediaType}" +
"\n작성일자: ${story.createdAt}" +
"\n내용: ${story.content}")
}, { error ->
Log.e(TAG, "스토리 가져오기 실패", error)
}).addTo(disposables)
스토리 구성 요소는 REST API 가이드 및 레퍼런스에서 확인할 수 있습니다.
권한 | 사전 설정 | 카카오 로그인 | 사용자 동의 | 레퍼런스 |
---|---|---|---|---|
- | 플랫폼 등록 카카오 로그인 활성화 동의 항목 |
필요 | 필요: 카카오스토리 글 목록 (story_read) |
Android SDKdelete() ReactiveX Android SDK delete() |
현재 로그인한 사용자의 카카오스토리에서 지정한 ID에 해당하는 스토리를 삭제합니다. 삭제할 스토리 게시물의 ID를 알고 있어야 요청 가능하므로, 내 스토리 가져오기 또는 스토리 쓰기 요청을 통해 해당 스토리의 ID를 확인하거나, 참고할 수 있는 스토리 ID 값이 있어야 합니다.
이름 | 타입 | 설명 | 필수 |
---|---|---|---|
id | String |
삭제할 스토리 ID | O |
다음은 현재 로그인한 사용자의 모든 스토리를 불러온 뒤, 그중 첫 번째 스토리를 삭제하는 예제입니다.
// 내 스토리 삭제하기
// 이 예제에서는 삭제고자 하는 스토리 아이디를 얻기 위해 전체 목록을 조회하고 첫번째 스토리 아이디를 사용했습니다.
StoryApiClient.instance.stories { stories, error ->
if (error != null) {
Log.e(TAG, "스토리 가져오기 실패", error)
}
else if (stories != null) {
if (stories.isEmpty()) {
Log.e(TAG, "내 스토리가 하나도 없어요 ㅠㅠ")
}
else {
// 삭제를 원하는 스토리 아이디
val storyId = stories.first().id
// 스토리 삭제하기
StoryApiClient.instance.delete(storyId) { error ->
if (error != null) {
Log.e(TAG, "스토리 삭제 실패", error)
} else {
Log.i(TAG, "스토리 삭제 성공 [$storyId]")
}
}
}
}
}
var disposables = CompositeDisposable()
// 내 스토리 삭제하기
// 이 예제에서는 삭제고자 하는 스토리 아이디를 얻기 위해 전체 목록을 조회하고 첫번째 스토리 아이디를 사용했습니다.
StoryApiClient.rx.stories()
.retryWhen(
// InsufficientScope 에러에 대해 추가 동의 후 재요청
RxAuthOperations.instance.incrementalAuthorizationRequired(context)
)
.flatMapCompletable { stories ->
if (stories.isNotEmpty()) {
// 삭제를 원하는 스토리 아이디
val storyId = stories.first().id
StoryApiClient.rx.delete(storyId)
}
else {
Completable.error(ClientError(ClientErrorCause.IllegalState, "내 스토리가 하나도 없어요 ㅠㅠ"))
}
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
Log.i(TAG, "스토리 삭제 성공")
}, { error ->
Log.e(TAG, "스토리 삭제 실패", error)
}).addTo(disposables)