Upgrade to PRO for Only $50/Year—Limited-Time Offer! 🔥
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
생산성을 높이는 Android SDK 배포 전략
Search
Yeojun Yoon
May 30, 2025
Technology
0
1
생산성을 높이는 Android SDK 배포 전략
주최: NAVER
행사: 2025 Engineering Day
날짜: 2025-05-30
제목: 생산성을 높이는 Android SDK 배포 전략
Yeojun Yoon
May 30, 2025
Tweet
Share
More Decks by Yeojun Yoon
See All by Yeojun Yoon
KMP + Supabase: 1인 개발의 새로운 패러다임
yjyoon
0
65
사이드 프로젝트를 20번 실패한 주니어의 오답노트 훔쳐보기(feat. KMP)
yjyoon
0
760
Other Decks in Technology
See All in Technology
AlmaLinux + KVM + Cockpit で始めるお手軽仮想化基盤 ~ 開発環境などでの利用を想定して ~
koedoyoshida
0
150
AWS re:Invent 2025 re:Cap LT大会 データベース好きが語る re:Invent 2025 データベースアップデート/セッションの紹介
coldairflow
0
150
ESXi のAIOps だ!2025冬
unnowataru
0
280
【開発を止めるな】機能追加と並行して進めるアーキテクチャ改善/Keep Shipping: Architecture Improvements Without Pausing Dev
bitkey
PRO
1
110
マイクロサービスへの5年間 ぶっちゃけ何をしてどうなったか
joker1007
18
7.4k
Oracle Database@Google Cloud:サービス概要のご紹介
oracle4engineer
PRO
1
740
普段使ってるClaude Skillsの紹介(by Notebooklm)
zerebom
8
1.9k
まだ間に合う! Agentic AI on AWSの現在地をやさしく一挙おさらい
minorun365
17
2.3k
AI with TiDD
shiraji
1
240
Building Serverless AI Memory with Mastra × AWS
vvatanabe
0
320
意外と知らない状態遷移テストの世界
nihonbuson
PRO
1
190
半年で、AIゼロ知識から AI中心開発組織の変革担当に至るまで
rfdnxbro
0
120
Featured
See All Featured
Art, The Web, and Tiny UX
lynnandtonic
304
21k
How to Talk to Developers About Accessibility
jct
1
83
Information Architects: The Missing Link in Design Systems
soysaucechin
0
710
Practical Orchestrator
shlominoach
190
11k
SEO in 2025: How to Prepare for the Future of Search
ipullrank
3
3.3k
The SEO identity crisis: Don't let AI make you average
varn
0
35
Noah Learner - AI + Me: how we built a GSC Bulk Export data pipeline
techseoconnect
PRO
0
72
Google's AI Overviews - The New Search
badams
0
870
A better future with KSS
kneath
240
18k
Optimizing for Happiness
mojombo
379
70k
Designing for humans not robots
tammielis
254
26k
Mind Mapping
helmedeiros
PRO
0
38
Transcript
ᤓᣦᥧὺwৈᾪ 2(63-(wwᑦⰢw› Ḣℶ (ww):
328)287 x ᑦⰢᆲwἺⶒw'32:)28-32%0w'311-8wԲw-89&w '8-327 x 6%(0)w 32:)28-32w09+-2wὺwᾪởⶒwᑦⰢw8%7/wԶᇢ x ᑦⰢwᾪwԫὖwⴺ႒wῆఏ⺊
ᑦⰢᆲwἺⶒ '32:)28-32%0w'311-8wԲ -89&w '8-327 '311-8wᖶ⥦wᬒῇw₶wஊw࣎ὶwᑦⰢᆲwἺⶒwᚂഒ᷻
32:)28-32%0w 311-87 *)%8 ',36) 6)*%'836 *-< )1%28-'w )67-32-2+ ᑦⰢ
)67-32 )0)%7)w38)
)0)%7)w38) xx )%896)7 • 7944368w 2(63-(ww8%6+)8 xx 9+w-<)7 • '36)zww*36w0%6+)w-2498
xx *-<'36)zww*36w0%6+)w-2498 xx xx *)%8zw7944368w 2(63-(ww8%6+)8 )1%28-'w )67-32-2+
6)0)%7)§40)%7)§%'8-32 )2)6%8)w6)0)%7)w7w&%7)(w32w8,)w'32:)28-32%0w'311-87w74)'x '32:)28-32%0w'311-87ww→ :)67-32ww→ ',%2+)w03+ww→
жᾪഒw፮ᥒw᷻ᾪ⫮wῆఏ⺊ -89&w '8-327wᆲwᾪởⶒwᑦⰢwᾪwжᾪഒw፮ᥒw᷻ᾪ⫮wῆఏ⺊ жᾪഒ ‶Ίᧂ ᣢ࣪ở жᾪഒ ‶Ίᧂ Ắᖶở '311-8
6)0)%7) 238) 6)0)%7)§40)%7) -89& '8-327
жᾪഒw፮ᥒw᷻ᾪ⫮wῆఏ⺊ -89&w '8-327wᆲwᾪởⶒwᑦⰢwᾪwжᾪഒw፮ᥒw᷻ᾪ⫮wῆఏ⺊ 86-++)6 -89&w '8-327 6)437-836=¨(-74%8', x+-8,9&;36/*03;692¨-*¨*%-096)x=10 w w◒
):)28w8=4)wԲ ᾲ⚎ⶎw;36/*03;wᩎ
on: push: branches: - main name: Release SDK jobs: release:
runs-on: ubuntu-latest steps: - name: Release please action uses: googleapis/release-please-action@v4 id: release with: token: ${{ secrets.RELEASE_PLEASE_TOKEN }} - name: Update guide repository if: steps.release.outputs.releases_created == 'true' env: json: ${{ toJSON(steps.release.outputs) }} run: | curl -X POST \ -H "Accept: application/vnd.github+json" \ -H "Authorization: Bearer <GITHUB-TOKEN>" \ https://api.github.com/repos/android-sdk/guide/dispatches \ -d '{"event_type":"release-pr","client_payload":{"release_outputs":${{json}}}' )437-836= 6)0)%7)§40)%7)wӦԲwᤓᥧwḢᖶ 6)0)%7)§40)%7)wӦԲw›⓪wᾪ⥦ (-74%8',w w›᧗wᾪ⥦
on: repository_dispatch: types: [release-pr] name: Create new release PR jobs:
new-release-pr: runs-on: ubuntu-latest steps: - name: Update version run: | new_version=${{ github.event.client_payload.release_outputs.version }} version_path="config.toml" sed -i "s@version = \".*\"@version = \"${new_version}\"@g" "${version_path}" - name: Update release note run: | new_release_note=${{ github.event.client_payload.release_outputs.body }} release_note_path="docs/release_note.md" sed -i "1i ${new_release_note}"$'\n\n' "${release_note_path}" - name: Commit new release run: # . . . - name: Create new release PR run: # . . . 9-() )437-836= ›ଢᑑὶw6)0)%7)wᒺ› ›ଢᑑὶw',%2+)w03+wᔮ፮ :)6-732 ፮ῆḪw⚎⺎ 6)0)%7)w238)w࣪ởwὍw℺wᣳΆ - version = 8.3.0 + version = 8.4.0 '32*-+x8310 + ## 8.4.0 + + ### Features + + - support A + - support B + ## 8.3.0 6)0)%7)¨238)x1(
ᤎⵂᶧw▒ᬖ⺊wᑅw⦂⫮wῆఏ⺊ -89&w '8-327wᆲwᾪởⶒwᑦⰢwᾪwᤎⵂᶧw▒ᬖ⺊wᑅw⦂⫮wῆఏ⺊ '311-8 6)0)%7) 238) 6)0)%7)§40)%7) -89&
'8-327 ᤎⵂᶧ ‶Ίᧂ ᣢ࣪ở ᤎⵂᶧ ‶Ίᧂ Ắᖶở
on: push: branches: - main name: Release SDK jobs: release:
runs-on: ubuntu-latest steps: - name: Release please action uses: googleapis/release-please-action@v4 id: release with: token: ${{ secrets.RELEASE_PLEASE_TOKEN }} - name: Update sample app repository if: steps.release.outputs.releases_created == 'true' env: json: ${{ toJSON(steps.release.outputs) }} run: | curl -X POST \ -H "Accept: application/vnd.github+json" \ -H "Authorization: Bearer <GITHUB-TOKEN>" \ https://api.github.com/repos/android-sdk/sample/dispatches \ -d '{"event_type":"release-pr","client_payload":{"release_outputs":${{json}}}' )437-836=
on: repository_dispatch: types: [release-pr] name: Create new release PR jobs:
new-release-pr: runs-on: ubuntu-latest steps: - name: Update version catalog run: | new_version=${{ github.event.client_payload.release_outputs.version }} name="my-sdk" path="gradle/libs.versions.toml" sed -i "" "s@^${name} = \".*\"@${name} = \\"${new_version}\\"@" "${path}" - name: Build Test run: ./gradlew assembleDebug - name: Android Test run: ./gradlew connectedDebugAndroidTest - name: Commit new version catalog run: # . . . - name: Create new release PR run: # . . . %140)w 44 )437-836= )67-32w %8%03+w⭂ᾲwᩎ⁋ 9-0(wᑅw 2(63-(w)78wᩎ [versions] - my-sdk = 8.3.0 + my-sdk = 8.4.0 0-&7x:)67-327x8310
'311-8wԲw-89&w '8-327wᆲwᾪởⶒwᑦⰢwῆఏ⺊wⴺ႒ '311-8wὲ႒ᖶ⥦wᒺ›ywᇪᇢ↾wম⫮ywжᾪഒywᤎⵂᶧywخᇢԖwᚂഒw⦂⫮ڂ⇶ жᾪഒ ‶Ίᧂ ᣢ࣪ở жᾪഒ ‶Ίᧂ Ắᖶở '311-8
6)0)%7) 238) 6)0)%7)§40)%7) -89& '8-327 ᤎⵂᶧ ‶Ίᧂ ᣢ࣪ở ᤎⵂᶧ ‶Ίᧂ Ắᖶở
'311-8wᬒwℲᾎⶎԖwιw⁆ *)%8zw7944368w%8-:)w (w-28)+6%8-32 *)%8zw-140)1)28w8-80) -); *)%8zw-140)1)28w1)(-% -); 6)*%'836zw6)2%1)w731)w:%6-%&0)7 *-<zw8-80) -);
,%7w731)w&9+7 xwxwx ⶒwж⇶w*)%896)wḆwଶⶪᥒwⶎ࣎ᾎw'311-8wሂ
'311-8wᬒwℲᾎⶎԖwιw⁆ ᾪᐮwἆӟw‶ΊᧂḆwṢྲкw'311-8wᾎwᩎ⁋ὶw*-<94wὲ႒ *)%8zw7944368w2);w*)%896) ():)034 *-<zw&9+w32w2);w*)%896) *)%8zw7944368w2);w*)%896) ():)034 *-<94|w*)%8zw7944368w2);w*)%896)
'311-8wᬒwℲᾎⶎԖwιw⁆ Ủᇢжwᵺଂwὖ‶ᆲwἺⶒw'311-8w1)77%+) *-<zw6)730:)w-779)w *-<'36)zw1)136=w0)%/w32wMyClass
'311-8wᬒwℲᾎⶎԖwιw⁆ )6+)w4900w6)59)78ww*631w2%:)6)<%140) )6+)w4900w6)59)78 ↓ )&%7)w%2(w1)6+) ᖾỊⶒw1)6+)w'311-8wଶᬖw6)&%7)
6%(0)w 32:)28-32w09+-2wὺwᾪởⶒ ᑦⰢw8%7/wԶᇢ ḢဢwዞḆᥒᾎwᑦⰢw8%7/wṶwṫᦎwᥚ⁋ὺwԫ⧫ԖwᾲԶӂ
2(63-(wwᑦⰢw‶Ίᧂ %:)2w )286%0w‶ΊᧂṶw*63+ 68-*%'836= 49&0-7, Ắᖶởw ᣢ࣪ởw
ዞwᑅw*0%:36wᓺ႒wᾪⶒwᑦⰢwᥚ⁋ 63(9'8w0%:36wḆw൦ᆮwᚂഒw֢ᖺwᑅwዞḆw൦ᆮwᑦⰢwԲ⁋wᾪ -28)62%0 )<8)62%0 63(9'8w0%:367 ‶Ίᧂ ዞ 396')7%6 63+9%6( -')27)
wዞ wዞ wዞ .%:%(3'wⰢ wዞ wዞ wዞ .%:%(3'wⰢ
ᔫῗⶎԖwⅇᔫw+6%(0) 8%7/wԶᇢ 6%(0)w 32:)28-32w09+-2wὺw⺒ởⶎḢwᑦⰢw8%7/wᆲwⶒwԩḆᥒwԶᇢ wዞwᑦⰢw8%7/ wዞwᑦⰢw8%7/ wዞwᑦⰢw8%7/ wዞwᑦⰢw8%7/ wዞ wዞ
wዞ wዞ
ᔫῗⶎԖwⅇᔫw+6%(0) 8%7/wԶᇢ 6%(0)w 32:)28-32w09+-2wὺw⺒ởⶎḢwᑦⰢw8%7/wᆲwⶒwԩḆᥒwԶᇢ wዞ wዞ wዞ wዞ 32:)28-32 09+-2
ᑦⰢw6%(0)w 32:)28-32w09+-2wῇᥧⶎ٦ 6%(0)w 32:)28-32w09+-2wὺw⺒ởⶎḢwᑦⰢw8%7/wᆲwⶒwԩḆᥒwԶᇢ &9-0(§03+-''32:)28-32 plugins { `kotlin-dsl` } group
= "com.example.buildlogic" java { sourceCompatibility = VERSION_17 targetCompatibility = VERSION_17 } kotlin { compilerOptions { jvmTarget = JvmTarget.JVM_17 } } &9-0(§03+-''32:)28-32&9-0(x+6%(0)x/87 pluginManagement { repositories { mavenCenteral() google() gradlePluginPortal() } includeBuild("build-logic") } 7)88-2+7x+6%(0)x/87
ᑦⰢw6%(0)w 32:)28-32w09+-2wῇᥧⶎ٦ 6%(0)w 32:)28-32w09+-2wὺw⺒ởⶎḢwᑦⰢw8%7/wᆲwⶒwԩḆᥒwԶᇢ /* . . . */ dependencies
{ compileOnly("com.vanniktech:gradle-maven-publish-plugin") /* . . . */ } &9-0(§03+-''32:)28-32&9-0(x+6%(0)x/87 ,8847z+-8,9&x'31:%22-/8)',+6%(0)§1%:)2§49&0-7,§409+-2 class PublishConventionPlugin : Plugin<Project> { override fun apply(target: Project) { with(target) { apply<MavenPublishBasePlugin>() configure<MavenPublishBaseExtension> { group = project.groupId version = project.versionName publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL) signAllPublications() pomFromGradleProperties() } } } } &9-0(§03+-''32:)28-32 9&0-7, 32:)28-3209+-2x/8
ᑦⰢw6%(0)w 32:)28-32w09+-2wῇᥧⶎ٦ 6%(0)w 32:)28-32w09+-2wὺw⺒ởⶎḢwᑦⰢw8%7/wᆲwⶒwԩḆᥒwԶᇢ class PublishConventionPlugin : Plugin<Project> { override
fun apply(target: Project) { with(target) { apply<MavenPublishBasePlugin>() configure<MavenPublishBaseExtension> { group = project.groupId version = project.versionName publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL) publishToJFrogArtifactory() signAllPublications() pomFromGradleProperties() } } } } &9-0(§03+-''32:)28-32 9&0-7, 32:)28-3209+-2x/8 private fun Project.publishToJFrogArtifactory() { extensions.getByType<PublishingExtension>() .repositories.maven { name = "jFrogArtifactory" url = uri("https://example.com/artifactory") with(project) { if (hasProperty("jFrogUsername") && hasProperty("jFrogPassword") ) { credentials { username = property("jFrogUsername").toString() password = property("jFrogPassword").toString() } } } } } &9-0(§03+-''32:)28-32 9&0-7, 32:)28-3209+-2x/8
ᑦⰢw6%(0)w 32:)28-32w09+-2wῇᥧⶎ٦ 6%(0)w 32:)28-32w09+-2wὺw⺒ởⶎḢwᑦⰢw8%7/wᆲwⶒwԩḆᥒwԶᇢ open class PublishConventionExtension @Inject constructor( objectFactory:
ObjectFactory ) { @Input val publishType: Property<PublishType> = objectFactory .property<PublishType>() .convention(PublishType.ALL) // default value @Input val publishJavadoc: Property<Boolean> = objectFactory .property<Boolean>() .convention(false) // default value } enum class PublishType { ALL, INTERNAL_ONLY, EXTERNAL_ONLY } &9-0(§03+-''32:)28-32 9&0-7, 32:)28-32<8)27-32x/8 class PublishConventionPlugin : Plugin<Project> { override fun apply(target: Project) { with(target) { apply<MavenPublishBasePlugin>() configure<MavenPublishBaseExtension> { /* . . . */ val extension = extensions .create<PublishConventionExtension>( name = "publishOptions" ) configurePublishing(extension) } } } } &9-0(§03+-''32:)28-32 9&0-7, 32:)28-3209+-2x/8 publishOptions { publishType = INTERNAL_ONLY publishJavadoc = true }
ᑦⰢw6%(0)w 32:)28-32w09+-2wῇᥧⶎ٦ 6%(0)w 32:)28-32w09+-2wὺw⺒ởⶎḢwᑦⰢw8%7/wᆲwⶒwԩḆᥒwԶᇢ internal fun Project.configurePublishing(extension: PublishConventionExtension) { afterEvaluate
{ if (extension.publishJavadoc.get()) { apply<DokkaConventionPlugin>() } extensions.configure<PublishingExtension> { publications { val publishType = extension.publishType.get() if (publishType == PublishType.ALL || publishType == PublishType.INTERNAL_ONLY) { createMavenPublication(project = project, flavor = "internal") } if (publishType == PublishType.ALL || publishType == PublishType.EXTERNAL_ONLY) { createMavenPublication(project = project, flavor = "external") } } tasks.withType<PublishToMavenRepository>().matching { task -> val (repositoryName, publicationName) = with(task) { repository.name to publication.name } repositoryName == "jFrogArtifactory" && publicationName.startWith("external") || repositoryName == "mavenCentral" && publicationName.startWith("internal") }.configureEach { enabled = false } } } &9-0(§03+-''32:)28-32 9&0-7, 32*-+x/8 49&0-7,=4) ᓺwᑦⰢwṫᦎwᖺ٦ ዞwᓺw.%:%(3' ᑦⰢwḢᖶwᖺ٦ *0%:36wᓺwᑦⰢw‶Ίᧂwᖺ٦
ᑦⰢw6%(0)w 32:)28-32w09+-2wῇᥧⶎ٦ 6%(0)w 32:)28-32w09+-2wὺw⺒ởⶎḢwᑦⰢw8%7/wᆲwⶒwԩḆᥒwԶᇢ private fun PublisherContainer.createMavenPublication(project: Project, flavor: String)
{ create<MavenPublication>(flavor) { if (flavor == "internal") { project.extensions.findByType<LibraryExtension>()?.let { android -> artifact( project.tasks.create<Jar>("androidInternalSourcesJar") { archiveClassifier.set("sources") archiveFileName.set("internal-sources.jar") from( android.sourcesSets.getByName("main").java.srcDirs android.sourcesSets.getByName("internal").java.srcDirs ) } ) } } if (flavor == "external") { pom.license { /* . . . */ } } artifactId = project.artifactId + flavor /* . . . */ } &9-0(§03+-''32:)28-32 9&0-7, 32*-+x/8 )<8)62%0wởw431wᥚ⁋ -28)62%0wởw7396')7%6 ᑦⰢ %68-*%'8( Ḇ *0%:36wᣳΆ
ᑦⰢw6%(0)w 32:)28-32w09+-2wῇᥧⶎ٦ 6%(0)w 32:)28-32w09+-2wὺw⺒ởⶎḢwᑦⰢw8%7/wᆲwⶒwԩḆᥒwԶᇢ plugins { `kotlin-dsl` } /* .
. . */ gradlePlugin { plugins { register("examplePublish") { id = "example.convention.publish" implementationClass = "PublishConventionPlugin" } } } &9-0(§03+-''32:)28-32&9-0(x+6%(0)x/87 plugins { id("example.convention.publish") /* . . . */ } publishOptions { publishType = PublishType.ALL publishJavadoc = false } /* . . . */ *)%896),31)&9-0(x+6%(0)x/87 plugins { id("example.convention.publish") /* . . . */ } publishOptions { publishType = PublishType.INTERNAL_ONLY publishJavadoc = true } /* . . . */ *)%896)&-00-2+&9-0(x+6%(0)x/87
ᑦⰢwᾪwԫὖwⴺ႒wῆఏ⺊ ᑦⰢжw࣒wwᷪේw⁋ᔪᆲwᷪൊḆwᷪӂwԫὖⶖڂ}
ᑦⰢwᾪwԫὖwⴺ႒wῆఏ⺊ ᑦⰢwᾪwᤎⵂᶧwᚂഒwᑅwᣢ࣪wᚂഒw‶Ίᧂw᷻႒ഒ ᣢ࣪wᚂഒw‶Ίᧂw᷻႒ഒ (3//% ፮ᥒwᚂഒ ሚ⓪w0%'/w⑺ॆwᶂᇲ 690)6wᖺᥓwᇢⰢ⫮ '311-8 6)0)%7) 238)
6)0)%7)§40)%7) -89& '8-327 ᤎⵂᶧ ‶Ίᧂ ᣢ࣪ở ᤎⵂᶧ ‶Ίᧂ Ắᖶở ᣢ࣪wᚂഒ ‶Ίᧂ
ᑦⰢwᾪwԫὖwⴺ႒wῆఏ⺊ ᑦⰢwᾪw(3//%wᆲwᾪởⶒw.%:%(3' ᚂഒwᑅ +,§4%+)7wᑦⰢ ᣢ࣪wᚂഒw‶Ίᧂw᷻႒ഒ (3//% ፮ᥒwᚂഒ ሚ⓪w0%'/w⑺ॆwᶂᇲ 690)6wᖺᥓwᇢⰢ⫮
ᑦⰢwᾪwԫὖwⴺ႒wῆఏ⺊ ᑦⰢwᾪ Զၞwሚ⓪wᑅw w࿏w⑺ॆwᶂᇲ ᣢ࣪wᚂഒw‶Ίᧂw᷻႒ഒ (3//%w፮ᥒ ᚂഒ ሚ⓪w0%'/w⑺ॆwᶂᇲ 690)6wᖺᥓwᇢⰢ⫮ https://github.com/naver/nam-sdk-android/releases.tag/v8.4.1
https://naver.github.io/nam-sdk-android https://naver.github.io/nam-sdk-guide/android https://사내빌드저장소.com/app/android/gfp-sdk/real/latest https://사내빌드저장소.com/app/android/gfp-sdk/real w⑺ॆᾲwᬒwᤎⵂᶧwᇷ⢢w›ଢ
ᑦⰢwᾪwԫὖwⴺ႒wῆఏ⺊ ᑦⰢwᾪw690)6wᆲwᾪởⶒwᤎⵂᶧwởwᖺᥓwᇢⰢ⫮w◊◒ ᣢ࣪wᚂഒw‶Ίᧂw᷻႒ഒ (3//%w፮ᥒ ᚂഒ ሚ⓪w0%'/w⑺ॆwᶂᇲ 690)6wᖺᥓwᇢⰢ⫮ ,8847z+-8,9&x'317438-*=690)6
2(w3*w39'91)287