Share
Embed
そのAPI、誰のため? Androidライブラリ設計における利用者目線の実践テクニック
DroidKaigi 2025
https://2025.droidkaigi.jp/timetable/946622/
mkeeda
September 12, 2025
Transcript
41 slides
-
そのAPI、誰のため? Androidライブラリ設計における 利 用 者 目 線の実践テクニック DroidKaigi 2 0
2 5 サイボウズ株式会社 向井 田 一 平 (@mr_mkeeda) -
• 向井 田 一 平 (むかいだ いっぺい) • 𝕏 :
@mr_mkeeda • Github: @mkeeda • Android Developer at Cybozu, Inc • 社内ライブラリ開発 2 -
認証ライブラリを作ろう • サイボウズのモバイルアプリは認証処理が 大 変 • 既存アプリの認証機能を再利 用 できるようにしよう! •
アプリの認証コードを抜き出してライブラリ化 4 アプリA 認証機能 アプリA 認証機能 アプリB アプリB 認証機能 -
めっちゃ使いにくいライブラリ • 元アプリの内部設計を知らないと使えないAPI • 特殊なインスタンス管理 • いろいろな処理をやりすぎているクラス/関数 • ドキュメントない •
だんだんと負債化 • 分からないコード & 壊すと怖い機能 5 -
失敗の原因 • 一 度作ったものを再利 用 したい、という気持ちは良かった • 実装をそのまま再利 用 することしか考えていなかったのが問題
• どの処理を別モジュールにするのかを考えていた • 切り出したモジュールを利 用 する側の視点で考えていなかった 6 -
今 日 の話:使いやすいライブラリの作り 方 • ゴール • ライブラリ開発者が、ライブラリを使いやすくするヒントを得る • 話すこと
• 利 用 者フレンドリーなAPIにするためのテクニック集 • 話さないこと • ベストプラクティス 7 -
使いやすいライブラリを作るときのポイント 1 . カプセル化 2 . 前提にできる技術スタック 3 . 機能性と
自 由度 4 . 利 用 者に説明してみる 5 . 利 用 者になってみる 8 -
ポイント1: カプセル化 • 「互いに関連するデータとロジックなどを1つのモジュールとしてまとめ ることである。また、より広い意味ではまとめたモジュールの内側の詳細 を外側から隠蔽することをも含む。」 • 利 用 者は公開されているものすべてを利
用 可能なAPIと 見 做す • 使われると困るもの/使う必要のないものは隠そう 9 https://ja.wikipedia.org/wiki/カプセル化 -
可視性修飾 子 (Visibility modi fi er s ) を使う •
public:利 用 者に使わせるAPI • internal:ライブラリ内部では使いまわしたいAPI • private:上記以外全部 • public APIの使い勝 手 が 大 事 1 0 -
Explicit API Mode • すべてのAPIに可視性修飾 子 の付与を強制させるコンパイラ機能 • ついうっかりpublicになってしまってた!を防げる •
強制的にpublicを書くことで、利 用 者が使えるAPIを意識できる • Kotlinのデフォルトの可視性はpublic 1 1 -
内部実装を知らなくても使えるコンストラクタ • コンストラクタパラメータには2種類ある • クラスの挙動を決めるパラメータ • 実装の都合で必要なパラメータ • 利 用
者は「クラスの挙動を決めるパラメータ」だけ指定できると親切 1 3 -
1 4 val client = MyApiClient( apiKey = "your_api_key", httpClient
= OkHttpClient.Builder() .addInterceptor(MyInterceptor()) .build(), ) public class MyApiClient( private val apiKey: String, private val httpClient: OkHttpClient, ) ライブラリ側 アプリ側 -
val client = MyApiClient( apiKey = "your_api_key", httpClient = OkHttpClient.Builder()
.addInterceptor(MyInterceptor()) .build(), ) public class MyApiClient( private val apiKey: String, private val httpClient: OkHttpClient, ) 1 5 クラスの挙動を決めるパラメータ 実装の都合で必要なパラメータ (DIオブジェクト) ←利 用 者にDIさせるの? ライブラリ側 アプリ側 -
1 6 public class MyApiClient internal constructor( private val apiKey:
String, private val httpClient: OkHttpClient, ) { public constructor(apiKey: String) : this( apiKey = apiKey, httpClient = OkHttpClient.Builder() .addInterceptor(MyInterceptor()) .build(), ) } ← 内部実装 用 ← 利 用 者 用 ← シンプルに ライブラリ側 アプリ側 val client = MyApiClient(apiKey = "your_api_key") -
• ライブラリ内のモジュール依存関係も 内部実装の 一 部 • module Aのpublic APIの型は、 module
Aへの依存だけで解決できる? モジュール依存関係をカプセル化 1 7 Library module A ??? App public class A -
module Aへの依存だけで型解決できる場合 1 8 Library module A Library module B
App public class A { private val b = B() } public class B build.gradle.kts (App) dependencies { implementation(":moduleA") } build.gradle.kts (Library module A) dependencies { implementation(":moduleB") } -
module Aの内部的な依存関係が漏れてる例 1 9 Library module A Library module B
App public class A { public val b = B() } public class B build.gradle.kts (App) dependencies { implementation(":moduleA") implementation(":moduleB") ⭐ } build.gradle.kts (Library module A) dependencies { implementation(":moduleB") ⭐ } -
module Aがmobule Bの依存を伝搬する 2 0 Library module A Library module
B App public class B build.gradle.kts (App) dependencies { implementation(":moduleA") } build.gradle.kts (Library module A) dependencies { api(":moduleB") ⭐ } public class A { public val b = B() } -
使いやすいライブラリを作るときのポイント 1 . カプセル化 2 . 前提にできる技術スタック 3 . 機能性と
自 由度 4 . 利 用 者に説明してみる 5 . 利 用 者になってみる 2 1 -
ポイント2: 前提にできる技術スタック • アプリ開発の場合、 自 分たちの都合だけで決められる 2 2 アプリA •
UI:Compose • 非 同期処理:Kotlin Coroutine • DI:Koin -
ポイント2: 前提にできる技術スタック • ライブラリの場合は、利 用 側アプリの技術スタックを考慮する必要がある 2 3 アプリA •
UI:Compose • 非 同期処理:Kotlin Coroutine • DI:Koin アプリB • UI:AndroidView • 非 同期処理:RxJava • DI:Hilt 認証ライブラリ • UI:??? • 非 同期処理:??? • DI:??? -
例:ライブラリでHiltを使う 2 4 @HiltAndroidApp class MyApp : Application() @AndroidEntryPoint class
MainActivity : ComponentActivity() public class PasswordAuthenticator @Inject constructor( private val httpClient: HttpClient ) ライブラリ側 アプリ側 -
例:ライブラリでHiltを使う • ライブラリのために、利 用 者がHiltの設定をする必要がある • 利 用 者は快くやってくれるか? •
もし利 用 側アプリがHilt以外のDIフレームワークを採 用 していると? • 世の中のライブラリはだいたい 手 動でDIしている • 私も 手 動DIを選択した • 社内で技術スタックを揃えている場合はよさそう 2 5 -
例:画 面 遷移を含むUIの提供 • 認証フローUIをライブラリで提供する場合 (ex. 環境 入力 →パスワード 入力
→ワンタイムパスワード 入力 ) 2 6 アプリ 画 面 A 画 面 B 認証ライブラリ 環境 入力 画 面 パスワード画 面 ワンタイムパスワード 画 面 -
2 7 public fun NavGraphBuilder.authentication() composable<ConnectionEnvironmentScreen> composable<PasswordLoginScreen> { } composable<OneTimeTokenScreen>
{ } } NavHost( ... ) { composable<ScreenA> { } composable<ScreenB> { } authentication() } ライブラリ側 アプリ側 • Navigation Composeの利 用 を 前提としたインターフェース • Navigation Composeを 採 用 してないアプリには 適 用 しにくい -
ライブラリ側 アプリ側 @Composable public fun Authentication() { NavHost( ... )
{ composable<ConnectionEnvironmentScreen> { } composable<PasswordLoginScreen> { } composable<OneTimeTokenScreen> { } } } // ݺͼग़͠ Authentication() // Navigation Compose val navController = rememberNavController() NavHost( navController, startDestination = ScreenA ) { composable<ScreenA> { } composable<ScreenB> { } composable<AuthenticationScreen> { Authentication() } } • 画 面 遷移実装をカプセル化した インターフェース • アプリ側の画 面 遷移実装に依らず 自 由に利 用 できる -
使いやすいライブラリを作るときのポイント 1 . カプセル化 2 . 前提にできる技術スタック 3 . 機能性と
自 由度 4 . 利 用 者に説明してみる 5 . 利 用 者になってみる 2 9 -
ポイント3: 機能性と 自 由度 • 利 用 者の気持ち • ライブラリにたくさんの仕事をやってほしい(機能性)
• ライブラリの挙動を 自 分好みに変更したい( 自 由度) • 多くの機能を実現するほど、利 用 者が直接コントロールできる要素は減る 3 0 -
3 1 @Composable public fun Authentication( onSuccess: (Session) -> Unit
) public class PasswordAuthenticator { public fun authenticate( username: String, password: String ): Session } 認証フロー実 行 するUIコンポーネント 環境 入力 →パスワード 入力 →ワンタイムパスワード 入力 の順で実 行 認証が完了すると、有効なセッションを返す パスワード認証を実 行 する 認証が完了すると有効なセッションを返す -
機能性と 自 由度を両 立 する • Composeは機能性と 自 由度を両 立
する構造 • 高 レベルのモジュールは機能が多めのAPI (Modi fi er.draggable, Modi fi er.scrollable) • 低レベルのAPIも公開されている (Modi fi er.pointerInput) • 機能が気に 入 らなければ 一 つレイヤーを下げて 利 用 者がカスタム実装できる 3 2 https://developer.android.com/develop/ui/compose/layering -
使いやすいライブラリを作るときのポイント 1 . カプセル化 2 . 前提にできる技術スタック 3 . 機能性と
自 由度 4 . 利 用 者に説明してみる 5 . 利 用 者になってみる 3 3 -
ドキュメントを書く効果 • 利 用 者 目 線)APIの意図や使い 方 がわかる •
開発者 目 線)利 用 者に伝える必要が情報を考える機会になる • どの程度フォローすれば使えるのか? • 暗黙になっていた前提に気づける • 説明しにくいAPIは責務が曖昧なのかも 3 5 -
私の場合 • まずはpublic APIのAPIリファレンスから始めた • APIを作ったPR内で書く • KDoc形式のコードコメントを書くだけ(リファレンスページは後回し) • 後にガイダンス
• リポジトリにmarkdownを含める形からスタート • ライブラリの全体的な使い 方 は、APIリファレンスでは説明しきれない • Android Developersのガイダンスはすごい参考になる 3 6 -
使いやすいライブラリを作るときのポイント 1 . カプセル化 2 . 前提にできる技術スタック 3 . 機能性と
自 由度 4 . 利 用 者に説明してみる 5 . 利 用 者になってみる 3 7 -
ポイント5: 利 用 者になってみる • ライブラリの使い勝 手 は、 自 分で使ってみるのが
一 番わかりやすい • サンプルアプリを作る • ライブラリの基本的な使い 方 を 示 すアプリ • READMEとは別に、実際に動くアプリを作る 3 8 -
世の中のサンプルアプリ • 有名どころのライブラリには サンプルアプリが 用 意されている • Coil https://github.com/coil-kt/coil/tree/main/ samples
• Navigation 3 https://github.com/androidx/androidx/tree/ androidx-main/navigation 3 /navigation 3 -ui/ samples • Circuit https://github.com/slackhq/circuit/tree/main/ samples 3 9 Coilの例 -
サンプルアプリを作るコツ • まずはシンプルな使い 方 のサンプルから • 機能ごとにコードを分ける • アプリモジュール単位 •
画 面 単位 • 壊れたら気付けるようにCIでビルド 4 0 https://github.com/android/nav 3 -recipes -
まとめ • 他 人 に使ってもらうコードを書くときは、利 用 者の気持ちを考えよう • ライブラリ開発じゃなくても当てはまる •
使いやすいライブラリを作るときのポイント 1 . カプセル化 2 . 前提にできる技術スタック 3 . 機能性と 自 由度 4 . 利 用 者に説明してみる 5 . 利 用 者になってみる 4 1