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

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

Sponsored · SiteGround - Reliable hosting with speed, security, and support you can count on.
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