Upgrade to Pro — share decks privately, control downloads, hide ads and more …

そのAPI、誰のため? Androidライブラリ設計における利用者目線の実践テクニック

Avatar for mkeeda mkeeda
September 12, 2025

そのAPI、誰のため? Androidライブラリ設計における利用者目線の実践テクニック

Avatar for mkeeda

mkeeda

September 12, 2025
Tweet

More Decks by mkeeda

Other Decks in Programming

Transcript

  1. • 向井 田 一 平 (むかいだ いっぺい) • 𝕏 :

    @mr_mkeeda • Github: @mkeeda • Android Developer at Cybozu, Inc • 社内ライブラリ開発 2
  2. 認証ライブラリを作ろう • サイボウズのモバイルアプリは認証処理が 大 変 • 既存アプリの認証機能を再利 用 できるようにしよう! •

    アプリの認証コードを抜き出してライブラリ化 4 アプリA 認証機能 アプリA 認証機能 アプリB アプリB 認証機能
  3. 失敗の原因 • 一 度作ったものを再利 用 したい、という気持ちは良かった • 実装をそのまま再利 用 することしか考えていなかったのが問題

    • どの処理を別モジュールにするのかを考えていた • 切り出したモジュールを利 用 する側の視点で考えていなかった 6
  4. 今 日 の話:使いやすいライブラリの作り 方 • ゴール • ライブラリ開発者が、ライブラリを使いやすくするヒントを得る • 話すこと

    • 利 用 者フレンドリーなAPIにするためのテクニック集 • 話さないこと • ベストプラクティス 7
  5. 可視性修飾 子 (Visibility modi fi er s ) を使う •

    public:利 用 者に使わせるAPI • internal:ライブラリ内部では使いまわしたいAPI • private:上記以外全部 • public APIの使い勝 手 が 大 事 1 0
  6. Explicit API Mode • すべてのAPIに可視性修飾 子 の付与を強制させるコンパイラ機能 • ついうっかりpublicになってしまってた!を防げる •

    強制的にpublicを書くことで、利 用 者が使えるAPIを意識できる • Kotlinのデフォルトの可視性はpublic 1 1
  7. 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, ) ライブラリ側 アプリ側
  8. 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させるの? ライブラリ側 アプリ側
  9. 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")
  10. • ライブラリ内のモジュール依存関係も 内部実装の 一 部 • module Aのpublic APIの型は、 module

    Aへの依存だけで解決できる? モジュール依存関係をカプセル化 1 7 Library module A ??? App public class A
  11. 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") }
  12. 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") ⭐ }
  13. 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() }
  14. ポイント2: 前提にできる技術スタック • ライブラリの場合は、利 用 側アプリの技術スタックを考慮する必要がある 2 3 アプリA •

    UI:Compose • 非 同期処理:Kotlin Coroutine • DI:Koin アプリB • UI:AndroidView • 非 同期処理:RxJava • DI:Hilt 認証ライブラリ • UI:??? • 非 同期処理:??? • DI:???
  15. 例:ライブラリでHiltを使う 2 4 @HiltAndroidApp class MyApp : Application() @AndroidEntryPoint class

    MainActivity : ComponentActivity() public class PasswordAuthenticator @Inject constructor( private val httpClient: HttpClient ) ライブラリ側 アプリ側
  16. 例:ライブラリでHiltを使う • ライブラリのために、利 用 者がHiltの設定をする必要がある • 利 用 者は快くやってくれるか? •

    もし利 用 側アプリがHilt以外のDIフレームワークを採 用 していると? • 世の中のライブラリはだいたい 手 動でDIしている • 私も 手 動DIを選択した • 社内で技術スタックを揃えている場合はよさそう 2 5
  17. 例:画 面 遷移を含むUIの提供 • 認証フローUIをライブラリで提供する場合 (ex. 環境 入力 →パスワード 入力

    →ワンタイムパスワード 入力 ) 2 6 アプリ 画 面 A 画 面 B 認証ライブラリ 環境 入力 画 面 パスワード画 面 ワンタイムパスワード 画 面
  18. 2 7 public fun NavGraphBuilder.authentication() composable<ConnectionEnvironmentScreen> composable<PasswordLoginScreen> { } composable<OneTimeTokenScreen>

    { } } NavHost( ... ) { composable<ScreenA> { } composable<ScreenB> { } authentication() } ライブラリ側 アプリ側 • Navigation Composeの利 用 を 前提としたインターフェース • Navigation Composeを 採 用 してないアプリには 適 用 しにくい
  19. ライブラリ側 アプリ側 @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() } } • 画 面 遷移実装をカプセル化した インターフェース • アプリ側の画 面 遷移実装に依らず 自 由に利 用 できる
  20. ポイント3: 機能性と 自 由度 • 利 用 者の気持ち • ライブラリにたくさんの仕事をやってほしい(機能性)

    • ライブラリの挙動を 自 分好みに変更したい( 自 由度) • 多くの機能を実現するほど、利 用 者が直接コントロールできる要素は減る 3 0
  21. 3 1 @Composable public fun Authentication( onSuccess: (Session) -> Unit

    ) public class PasswordAuthenticator { public fun authenticate( username: String, password: String ): Session } 認証フロー実 行 するUIコンポーネント 環境 入力 →パスワード 入力 →ワンタイムパスワード 入力 の順で実 行 認証が完了すると、有効なセッションを返す パスワード認証を実 行 する 認証が完了すると有効なセッションを返す
  22. 機能性と 自 由度を両 立 する • 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
  23. ドキュメントを書く効果 • 利 用 者 目 線)APIの意図や使い 方 がわかる •

    開発者 目 線)利 用 者に伝える必要が情報を考える機会になる • どの程度フォローすれば使えるのか? • 暗黙になっていた前提に気づける • 説明しにくいAPIは責務が曖昧なのかも 3 5
  24. 私の場合 • まずはpublic APIのAPIリファレンスから始めた • APIを作ったPR内で書く • KDoc形式のコードコメントを書くだけ(リファレンスページは後回し) • 後にガイダンス

    • リポジトリにmarkdownを含める形からスタート • ライブラリの全体的な使い 方 は、APIリファレンスでは説明しきれない • Android Developersのガイダンスはすごい参考になる 3 6
  25. ポイント5: 利 用 者になってみる • ライブラリの使い勝 手 は、 自 分で使ってみるのが

    一 番わかりやすい • サンプルアプリを作る • ライブラリの基本的な使い 方 を 示 すアプリ • READMEとは別に、実際に動くアプリを作る 3 8
  26. 世の中のサンプルアプリ • 有名どころのライブラリには サンプルアプリが 用 意されている • 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の例
  27. まとめ • 他 人 に使ってもらうコードを書くときは、利 用 者の気持ちを考えよう • ライブラリ開発じゃなくても当てはまる •

    使いやすいライブラリを作るときのポイント 1 . カプセル化 2 . 前提にできる技術スタック 3 . 機能性と 自 由度 4 . 利 用 者に説明してみる 5 . 利 用 者になってみる 4 1