月曜日, 9月 1, 2025
月曜日, 9月 1, 2025
- Advertisment -
ホームニューステックニュースZonePane の CMP による iOS 版開発について

ZonePane の CMP による iOS 版開発について


2025年6月〜8月にかけて実施した「ZonePane の iOS 版開発」における技術的な知見をまとめたものです。
レガシーな技術を比較的多く含むAndroidアプリの Kotlin Multiplatform / Compose Multiplatform (CMP) 構成への移行を行い、iOSへの移植を行った記録です。

備忘録的な側面もありますが、特にレガシーなAndroidアプリのKMP/CMP移行を検討されている方には、具体的な課題と解決策のヒントを提供できるかと思います。

AI(Cursor, Claude Code)も活用しましたが、特に大規模なリファクタリングや移行作業において、その効果を実感しました。

2010年に開発を開始したTwitter用アプリTwitPaneをベースとし、Mastodonに対応したアプリとしてZonePaneの開発が始まりました。2023年2月頃です。

その後、Misskey、Blueskyにも順次対応していきました。

アプリの構成

技術的なポイントとして、TwitPaneと同じソースコードを共有しながら複数のアプリを開発していることがあげられます。
具体的には1つのプロジェクト内に下記のアプリが存在します。

アプリ名 対応
ZonePane (ぞーぺん) Bluesky、Mastodon、Misskeyに対応
BluePane Blueskyに対応
TwitPane無料版 Bluesky、Mastodon、Misskeyに対応、Twitter対応(APIが利用不可のため実質非対応)
TwitPane有料版 Bluesky、Mastodon、Misskeyに対応、Twitter対応(APIが利用不可のため実質非対応)
ついぺんリサーチR X閲覧専用
たいぺん タイッツー対応

ビルドは下記のコマンドでAndroid版を生成できるようにしてあります。

Android版のZonePaneは Mastodon, Misskey, Bluesky に対応していますが、iOS版は2025年8月末時点では Bluesky にのみ対応しています。
(今後 MastodonとMisskey にも対応していく予定です)

なぜ Bluesky から対応したのか

iOS版を作るための技術として Compose Multiplatform (CMP) を前提としていますが、Android版は Mastodon と Misskey が Android View (RecyclerView) で、Bluesky が Compose (LazyColumn) で実装されており、CMP に一番近い構成で実装されている Bluesky が iOS に最短距離で対応ができる可能性が高いことから、まずは Bluesky から対応することとしました。

▼Android版の現状
   Mastodon : AndroidView+RecyclerView
   Misskey : AndroidView+RecyclerView
   Bluesky : Jetpack Compose+LazyColumn

▼iOS に対応するためには、、、
   AndroidView(RecyclerView) -> Jetpack Compose(LazyColumn) -> Compose Multiplatform(LazyColumn)
   という移行作業が必要

とはいえ Bluesky 部分も Compose 実装なのは「タイムラインの描画のみ」で、その周辺の「タップしたときのメニュー」や「いいねやリポスト等の確認画面」、アプリ自体のメイン画面や設定画面、投稿画面なども Android View で作られていることから、大幅に作り込む必要があります(ありました)。

いわば、フルCompose版のAndroidアプリを作り、それをSwiftからライブラリとして呼び出してiOS版を作るようなイメージです。ほとんど作り直しですね。

レイヤー構成

Android View (RecyclerView) + Compose (LazyColumn)
ViewPager + Fragment
ViewModel
Presenter
UseCase
Repository
domain
common

モジュール構成

common, domain が基本にあって、その上に shared/core, shared/ui_core, shared/core_compose などが載り、各種機能モジュール、ライブラリ、アプリが載る構成ですが、15年も維持しているアプリなのでまあカオスですね。。

CMP/iOS対応で core -> core+android_core や domain -> domain+android_domain のように Android 専用のモジュールが増えたりしていて、改めて整理する必要性を感じます。

ViewPager に載せる Fragment をアプリ内で「PagerFragment」と呼んでおり、これをまとめたモジュールを pf_bsky のように pf_* と称しています。

下記にモジュール構成の詳細を アプリ内のREADME.md より抜粋すて記載しましたが、長いので折りたたんでおきます。

モジュール構成の詳細(README.mdより抜粋)

アプリ一覧 (app_*)

アプリ名 パッケージ名 対応サービス KMP/CMP対応 ファイル数 説明
app_free com.twitpane Twitter, Bluesky, Mastodon, Misskey Android [11] TwitPane無料版
app_premium com.twitpane.premium Twitter, Bluesky, Mastodon, Misskey Android [9] TwitPane有料版
app_research com.twitpane.search Twitter Android [8] ついぺんリサーチR
app_zonepane com.zonepane Bluesky, Mastodon, Misskey Android [9] ZonePane(ぞーぺん)
app_taipane com.taipane タイッツー Android [8] たいぺん
app_bluepane net.bluepane Bluesky Android [8] BluePane (Bluesky専用)
app_zonepane_cmp com.zonepane.cmp Bluesky, Mastodon, Misskey KMP/CMP [6/12/9] ZonePane CMP版 (開発中)
iosApp (ZonePane for iOS) Bluesky, Mastodon, Misskey KMP/CMP iOS版 (SwiftUI + CMP)

モジュール一覧(アプリ用ライブラリ、Featureモジュール) (modules/*)

モジュール名 対応サービス KMP/CMP対応 ファイル数 説明
▼コア機能
app_base 全サービス Android [3] 各アプリの共通ベース
android_common 全サービス Android Android共通処理
android_core 全サービス Android [93] Android共通コア機能
android_db_zstd 全サービス Android [1] データベース用Zstd圧縮
android_domain 全サービス Android Androidドメイン層
android_main 全サービス Android [82] メイン機能
android_shared_api 全サービス Android [28] Android共有API
android_shared_impl 全サービス Android+Compose [1] Android共有実装
cmp_main 全サービス KMP/CMP [2/14/2] メイン機能 (CMP版)
▼認証・アカウント関連
login_bluesky Bluesky KMP対応 [5/4/0] Blueskyログイン
login_common 全サービス KMP対応 [3/8/0] ログイン共通処理
login_fediverse_bluesky Mastodon, Misskey, Bluesky Android [27] Fediverse+Blueskyログイン
login_ob_bluepane Android+Compose [3] BluePane用オンボーディング
login_ob_research Android+Compose [3] Research用オンボーディング
login_ob_taipane Android+Compose [6] TaiPane用オンボーディング
login_ob_twitpane Android+Compose [3] TwitPane用オンボーディング
login_ob_zonepane KMP対応 [1/2/0] ZonePane用オンボーディング
login_ob_zonepane_cmp KMP対応 [0/2/0] ZonePane CMP用オンボーディング
login_tw Twitter Android [7] Twitterログイン
▼ページャフラグメント系 (pf_*)
pf_api 全サービス KMP対応 [19/2/0] プラットフォームAPI
pf_bsky Bluesky KMP対応 [63/111/3] Blueskyプラットフォーム
pf_core 全サービス KMP対応 [26/24/0] プラットフォームコア
pf_fediverse Mastodon, Misskey Android [290] Fediverseプラットフォーム
pf_legacy_timeline Twitter, Mastodon, Misskey Android [26] レガシータイムライン
pf_tai タイッツー Android+Compose [52] タイッツープラットフォーム
pf_tw Twitter Android [172] Twitterプラットフォーム
▼投稿機能 (publish_*)
publish_bsky Bluesky KMP/CMP対応 [12/14/1] Bluesky投稿
publish_common 全サービス KMP/CMP対応 [38/6/2] 投稿共通処理
publish_cross 全サービス Android+Compose [18] クロスポスト
publish_fediverse Mastodon, Misskey Android+Compose [23] Fediverse投稿
publish_taittsuu タイッツー Android+Compose [10] タイッツー投稿
publish_tw Twitter Android [21] Twitter投稿
▼プロフィール編集 (profile_edit_*)
profile_edit_bsky Bluesky KMP/CMP対応 [2/2/1] Blueskyプロフィール編集
profile_edit_mky Misskey Android [3] Misskeyプロフィール編集
profile_edit_mst Mastodon Android [3] Mastodonプロフィール編集
profile_edit_tw Twitter Android [2] Twitterプロフィール編集
▼UI・表示関連
custom_emoji_picker Mastodon, Misskey Android [26] カスタム絵文字ピッカー
gallery 全サービス Android [4] ギャラリー
icon_api 全サービス Android [8] アイコンAPI
icon_impl 全サービス Android [6] アイコン実装
image_crop 全サービス Android+Compose [3] 画像クロップ
imageviewer 全サービス Android [6] 画像ビューア
movieplayer 全サービス Android+Compose [12] 動画プレイヤー
tab_edit Twitter, Mastodon, Misskey KMP/CMP対応 [14/9/0] タブ編集
timeline_renderer_api 全サービス Android [6] タイムライン描画API
timeline_renderer_impl Twitter, Mastodon, Misskey Android+Compose [51] タイムライン描画実装
timeline_repository Twitter, Mastodon, Misskey KMP対応 [57/12/0] タイムラインリポジトリ
▼ユースケース
(割愛)
▼ジョブ管理
(割愛)
▼その他機能
(割愛)

共有モジュール一覧 (shared/*)

モジュール名 対応サービス KMP/CMP対応 ファイル数 説明
auth_api 全サービス KMP対応 [1/5/0] 認証API
auth_impl 全サービス KMP対応 [0/2/0] 認証実装
billing_repository_api 全サービス Android [2] 課金リポジトリAPI
billing_repository_impl 全サービス Android [8] 課金リポジトリ実装
common 全サービス KMP/CMP対応 [50/26/12] 共通処理
config_api 全サービス KMP対応 [5/1/0] 設定API
config_impl 全サービス KMP/CMP対応 [58/30/0] 設定実装
core 全サービス KMP/CMP対応 [19/84/10] コア機能
core_bsky Bluesky KMP対応 [14/51/0] Blueskyコア機能
core_compose 全サービス KMP/CMP対応 [28/86/6] Compose機能
core_fediverse Mastodon, Misskey Android [107] Fediverseコア機能
core_taittsuu タイッツー Android [54] タイッツーコア機能
core_tw Twitter Android [90] Twitterコア機能
db_api 全サービス KMP対応 [4/31/0] データベースAPI
db_impl 全サービス KMP対応 [11/13/2] データベース実装
domain 全サービス KMP/CMP対応 [16/67/2] ドメイン層
domain_bsky Bluesky KMP対応 [0/5/0] Blueskyドメイン
domain_fediverse Mastodon, Misskey KMP対応 [5/4/0] Fediverseドメイン
domain_tw Twitter Android [6] Twitterドメイン
emoji_api 全サービス KMP対応 [5/4/0] 絵文字API
emoji_impl 全サービス KMP対応 [6/1/0] 絵文字実装
mediaurldispatcher_api 全サービス KMP対応 [1/4/0] メディアURLディスパッチャAPI
mediaurldispatcher_impl 全サービス Android [28] メディアURLディスパッチャ実装
ui_core 全サービス KMP/CMP対応 [74/33/0] UIコア
unicode_emoji_picker 全サービス KMP/CMP対応 [2/4/0] Unicode絵文字ピッカー

「ファイル数」のフォーマット説明:

  • KMP対応モジュール: [Android/Common/iOS] = [androidMain/commonMain/iosMain ディレクトリの.ktファイル数]
  • KMP非対応モジュール: [ファイル数] = [src/main/kotlin または src/main/java ディレクトリの.ktファイル数]
  • KMP対応の判定基準: commonMainディレクトリに.ktファイルが存在する場合

各アプリの機能と移行・開発作業

Bluesky を対象に、何を移行し、何を作ったのかを整理してみます。

機能や構成要素 作業 備考
アプリのメインフレーム CMP, Compose Navigation で新規作成
サイドナビゲーション CMP 移行 ほぼ作り直し、Android版と統合
オンボーディング、ログイン CMP 移行 わりとスムーズに移行できた
設定画面 CMP 移行 ほぼ作り直し、Android版もCompose化
Bluesky のタイムライン CMP 移行 描画だけはComposeなので同じだけどViewModelの移行が超大変だった
Bluesky の通知 CMP 移行 実装上はタイムラインと共通なので同時に作業を実施
Bluesky のリスト、フィード、プロフィールなど CMP 移行 それぞれ割と独立していてスムーズに進行した
DB KMP移行 SQLDelight に移行、しんどい
設定 KMP移行 Preferenceの移行、作り込むだけなので割とスムーズだった
テーマ管理システム KMP移行 設定さえできていればいいので割とスムーズだった

技術スタック

Android版 CMP/KMP
DI Koin Koin
DB SQLite SQLDelight
設定値の保存 Preferences Preferences+NSUserDefaultsで独自実装
画面遷移 Activity + Fragment (設定画面のみ Jetpack Navigation) Navigation Compose
通信 OkHttp ktor
JSON解析 Gson Kotlin Serialization
APIライブラリ Bluesky kbsky kbsky
APIライブラリ Mastodon mastodon4j mastodon4j(自力でKMP対応)
APIライブラリ Misskey misskey4j kmisskey

開発期間

iOS版の開発ということはXcodeでのビルドにMacが必要なわけですが、実は私自身Macの初心者でした。というわけで5月末にクロスポスト機能が完成間近のタイミングでMac mini(M4)を発注するところからのスタートでした。

※2010年代にMacBook Airを購入したこともありましたが数日触れただけでほとんど使わずに売り払いました。。

あ、基本的に土日も作業しています。途中のジョインアライブ、RSRの期間(計4日間)以外はほぼ休みなしです。

期間(2025年) やったこと 備考
▼6月 基盤整備、KMP/CMP導入
6/2 – 6/5 (4日間) Mac環境構築 Mac miniのキッティングから開発環境構築まで
6/6 – 6/7 (2日間) build-logic, ConventionPlugin導入 これをやらないとKMPのビルドすらできない
6/8 – 6/11 (4日間) KMP導入 Appleデベロッパー登録もこの期間に。
6/12 – 6/14 (3日間) CMP版試作 フルComposeとCompose Navigationのお試し
6/15 – 6/16 (2日間) Universal Post 表示対応 iOS実機で擬似的なポスト(※)を表示する。(※ 設定画面のプレビュー等で使用しているもの)
6/17 – 6/18 (2日間) Blueskyログイン ログインのロジックを実行する
6/19 タイトルバー
6/20 タブ、Deck試作
6/21 – 6/23 (3日間) ViewModel, UIState, Effect, Listener の KMP 対応
6/24 – 6/26 (3日間) 検索ロジックのViewModel
6/27 – 6/28 (2日間) その他のBlueskyタイムライン等のViewModelのKMP対応
6/29 Bluesky タイムライン Compose の CMP 移行 Compose の CMP 対応
6/30 Coordinator 導入 Bluesky タイムラインは複雑なので「タブ」を管理する Coordinator を導入。Fragment 相当
▼7月 DB対応、Bluesky機能作り込み
7/1 – 7/3 (3日間) アカウント一覧、DBのKMP対応 SQLDelight 移行、TestFlight版配布も開始。
7/4 投稿 テキストのみのシンプルな実装
7/5 いいね、リポスト、画像ビューア
7/6 – 7/8 サイドナビゲーション(NavidgationDrawer)、DBマイグレーション、新通知対応
7/9 プロフィール、画面遷移
7/10 – 7/14 (5日間) 設定 ボリュームがありしんどかった
7/15 – 7/16 (2日間) タブのカスタマイズ、動画プレイヤー
7/17 画像投稿対応
7/18 Mastodon4J の KMP 対応 突然のMastodon対応
7/19 – 7/20 (2日間) ジョインアライブのためお休み
7/21 いいね、リポスト確認
7/22 – 7/25 タップメニュー 表示、前後検索、カラーラベル、ユーザーサブメニューも
7/26 リスト一覧
7/27 フィード一覧
7/28 リスト作成・編集
7/30 プロフィール
7/31 – 8/1 (2日間) タブの追加 メインのViewModel+Screenを動的なタブ追加対応にする。しんどかった
▼8月 低優先度機能の作り込み、審査対応
8/2 チャット、Unicode絵文字Picker
8/3 広告導入
8/4 – 8/5 (2日間) オンボーディング
8/6 – 8/7 (2日間) ミュート設定
8/8 通知のタップメニュー
8/9 Android版の作業
8/10 不具合修正など
8/11 オンボーディング
8/12 返信制限対応、不具合修正
8/13 体調不良で休み
8/14 アカウントの並べ替え対応
8/15 – 8/16 (2日間) RSRのためお休み
8/17 画像縮小対応
8/18 – 8/19 (2日間) プロフィール編集
8/20 リロードボタン長押し対応
8/21 ダークテーマの動的切り替え対応、テーマとデザイン設定
8/22 – 8/23 (2日間) タブの長押しメニュー、画面遷移作り直し
8/24 – 8/25 (2日間) 審査提出、タップメニューのショートカット
8/26 – 8/27 (2日間) リジェクト対応、Android版のEdgeToEdge対応
8/28 リジェクト対応
8/29 審査完了、App Store で公開!
8/30 Android版不具合修正など、この記事を書く

expect/actual一覧

expect/actual で iOS と Android の実装を分離するのが定番です。

expect/actual で実装した部分を一覧化することで 2025年夏時点における iOS/Android の実装分離ポイントが見えてくるかも。

※下記以外に、DIで実装を分離しているパターンなどもあります。

概要
▼メイン
fun createImageLoader() ImageLoader (Coil3)
fun platformModule() Koinの実装分離
fun PlatformBackHandler() 戻る制御(Androidのみ)
class TrackingPermissionManager() iOS の Tracking 関連のブリッジ
▼タブ関連
fun executeMigration() タブデータの移行処理、旧バージョン分はiOS無関係なのでAndroid実装のままにするためexpectで分離
▼Compose (pf_bsky)
fun openMediaForBluesky() 画像ビューア・動画プレイヤー表示
fun showBsMediaUrlSubMenu() メディアサブメニュー表示(Android版のみなので分離)
fun showImportDesignConfirmDialog() デザインインポートダイアログ(Android版のみなので分離)
fun showDesignConfigDialogPresenter() 長押しメニューのデザイン設定項目(Android版のみなので分離)
▼プロフィール編集
fun saveImageFromUri() 画像保存
suspend fun uploadBlobIn() 画像アップロード
▼投稿
BlueskyPublishDelegate.convertVideoOrImagesToEmbed() 画像・動画変換
fun BlueskyPublishDelegate.downloadAndUploadLinkThumbnail() リンクサムネイル
fun BlueskyPublishDelegate.downloadLinkThumbnail() サムネイルダウンロード
fun BlueskyPublishDelegate.uploadMedias() メディア投稿
fun BlueskyPublishDelegate.uploadVideo() 動画投稿(未対応)
fun BlueskyPublishDelegate.uploadImage() 画像投稿
▼投稿共通(publish_common)
expect class ImagePicker() 画像ピッカー
expect class MediaPermissionManager() アクセス権限(Android用)
▼共通(common)
expect class LruCache iOSのLruCacheがないので独自実装
expect interface PrefUtil2Delegate 設定
expect interface PrefUtil2EditorDelegate 設定保存
expect object CodePointUtil コードポイント処理(性能重視のためネイティブ実装)
expect val Long.toCommaSeparatedString: String カンマ区切り文字列生成
expect object MyLog ログ出力
expect object PlatformBackupManager BackupManager はAndroidのみ
expect val MatchGroup.rangeCompat: IntRange MatchGroup.rangeの代用
expect object StringUtil2 正規表現の互換用
▼共通(core)
expect object PlatformUtil isAndroidやpackageName,shareやclipboard管理などいろいろ
expect class ClipboardManager クリップボード
expect object TPFontPathConfig
expect object SystemThemeDetector ダークモード検出
expect class GetAppInfoUseCase() アプリバージョン
expect object IOUtil2 ファイルI/O
expect object NetworkUtil コネクション確認など
expect class OfficialAppLauncher() 公式アプリ起動
expect object StorageUtil2 内部ストレージ等の操作
expect fun String.Companion.formatCompat String.format の互換実装
▼Compose (core_compose)
expect fun BlurAsyncImage Blur表示付きImage
expect fun ImageRequest.Builder.enableAnimation アニメーション制御
expect object TPComposeFontProvider 外部フォント指定
▼DB (db_impl)
expect object SqlDriverFactory SQLDelight
expect object ZstdCompressor Zstd圧縮対応
▼ドメイン (domain)
expect fun applyPlatformLocale など Locale設定
expect object PlatformTPDateTimeUtil2 日付・時刻の整形

しんどかったこと

改めて振り返ってみて CMP / iOS 対応でしんどかったことをざっくりと書いてみます。

Mac導入

そもそもCmdキー主体の操作に慣れるまでしんどかった。
AndroidStudioはWindowsに合わせてCtrlキー主体のキー設定にカスタマイズして使い始めた。
Karabiner-Elementsも導入したり。

この3ヶ月のiOS版開発で、私自身のメインPC/開発環境も Windows11 から Mac に変化し、1995年から約30年間使い続けてきたWindowsとおわかれするという大変革となりました。まあWindowsマシン自体は経理とかでどうしても必要なので残してありますが(Macの)WindowsApp経由でリモートアクセスする使い方になっています。

build-logic / KMP 導入環境

まず最初にこれがしんどかった。
applicationやライブラリモジュール用の共通部分を buildSrc 配下に書いていて、groovyファイルのapplyで共通化してたんだけど、これがあることで KMP のプラグインが導入できなかった。
そこで(泣きながら)全部 Convention Plugin 化して対応した。

アイコン (フォントベースのアイコン -> Material Icon ベースに変更)

メニュー等に使用するアイコンがフォントファイルベースの https://github.com/takke/IconicDroid を使っていたんだけど、当然これはCMPで使えないので Material Icon のファイルを取り込んで使えるようにした。

地味にこの環境を整備するだけで半日くらいかかった。

文字列リソース

TwitPane時代から複数言語対応を前提にしていたので、KMP/CMP対応部分は Compose Resources を利用した文字列リソース参照に書き換え。

Android用の大量のコードがあるのでこれと2重管理になるのを避けるために、Compose Resources 用の文字列リソースを Android 用にコピーするタスクを作って管理するようにしたり。

文字列、正規表現

MatchGroup.range が ios, android にはあるけど他のプラットフォームにはなくてIDE上エラーになるのであえて expect/actual で回避した。

日付時刻の扱い

iOS 版だけなぜか 2055 年になったりした。instant.toNSDate() 等で回避。

と、その前に java.util.Date が使えないのでまず Instant が必要で、かつ開発期間中に Instant の移行(kotlinx.datetime.Instant -> kotlin.time.Instant)もあったりした。

AnnotatedString.fromHtml がない

気合いで実装。

(利用するタグの種類が a と br 程度だったので簡単なパーサを書いて AnnotatedString を構築した)

LinkedList -> ArrayList (mutableListOf())

ポストの表現にLinkedListを使っていたけどKMPにはないのでArrayListで。性能的にはまあ十分かな。

動画再生

https://github.com/Chaintech-Network/ComposeMultiplatformMediaPlayer を使わせてもらいました。

画像ビューア

https://github.com/usuiat/Zoomable を使わせてもらいました。

設定画面が全部View

気合いで作り直し。

ざっくり1つの設定だけ Claude Code に移行させて、自前で細かく修正してリファレンス実装を1つ作り、それと同様に Claude Code に実装してもらう、という流れで PLAN の md ファイルを作って・・・、まあ 2025年8月時点で Claude Code にお願いするときの定番の流れで。

DB層の作り直し

気合いで SQLDelight 化。

こちらも設定画面と同様に Claude Code とペアプロ。

ViewModel

Effect で個別の実装を Fragment/CMP で分離したり、Listener を最初は Fragment/CMP 用で大きく2つ用意してそれぞれが動作する状態を維持しながら徐々に統合していったり、、、

両対応にするための実装箇所をどこにすべきかはけっこう混乱するのでクラス図を用意したりして、
(ViewPagerに載せる)Fragmentを前提としたComposeのScreen+ViewModelを ViewPager/HorizontalPager 両対応に作り直すということを繰り返しました。

アプリ内の独自テーマ対応

iOSのダークテーマの扱いが全然分からなくて苦労した。

アプリ内の独自テーマシステムは結果的にはKMPに移行するだけ(ほぼディレクトリ移動のみ)だったんだけど、これが書き換わったりライト系・ダーク系テーマの切り替えをアプリ全体に反映するためにはどうするのか、、結局アプリの Compose 全体を key {} で囲むようなダサい実装になったんだけど、試行錯誤がしんどかった。

App Store 審査

審査に出したらいきなり4件も指摘されてびっくりして泣いちゃいましたが、いずれも真っ当な指摘だったので淡々と修正し、結果的には1往復で審査完了しました。

指摘されたのは、
・ストアの掲載内容
・アプリ内の機能について(2件)
・広告関連(IDFA関連)
でした。

15年ほどかけて作ったAndroidアプリ(の一部)をiOS対応したわけですが、こう振り返ってみてもやっぱり1人で3ヶ月でやる量じゃないと思います。

AI時代のスピード感は人間が疲弊するので用法用量を守って正しく使いたいものです。
(・・・あ、APIの上限のことじゃないよ。。)

AndroidアプリのCMP対応を考えている方の参考になれば幸いです。

さて、Mastodon対応はどうやろうかな・・・。



Source link

Views: 1

RELATED ARTICLES

返事を書く

あなたのコメントを入力してください。
ここにあなたの名前を入力してください

- Advertisment -