[KMP]Android+iOSでFirebase Storageから画像を取ってくるまで
環境
- macOS Sonoma14.5
- Android Studio Koala Feature Drop | 2024.1.2
- Xcode Version 15.4
Kotlin Multiplatformの環境構築については他に記事がそれなりにあるので既にできているものとして省略します。
また、全体コードはGitHubで公開しています。
何を作るか
サンプルプロジェクトとして、Firebase Storageから画像を取ってきて表示するシンプルなAndroid/iOSアプリを実装します。
Android | iPhone |
---|---|
プロジェクト作成
まず https://kmp.jetbrains.com でプロジェクトの作成を行います
以下命名について少し触れますが、完全に個人の見解です。
Project Name,Project IDの命名
Project Nameはandroid,iOSのアプリ名,iOSのBundle IDの一部に利用されます
英数字、ハイフン、アンダースコアが利用可能ですが、Bundle IDがアンダースコア利用不可なので特段こだわりがなければアンダースコアは利用しないのが無難です。アンダースコアを利用してもBundle IDが書かれているファイルでアンダースコアをハイフンなどに置き換えれば問題はないと思われます。
Project IDはandroidのパッケージ名やiOSのBundle IDに利用されます。なので逆ドメイン形式で書きましょう。同じく英数字、ハイフン、アンダースコアが利用可能ですが、同じ理由でアンダースコアは利用しないのが無難です。
Downloadボタンをクリックしてそのフォルダをandroid studioなどで開くとビルドが走ります。
Firebase側の設定
Firebaseのプロジェクトを作成するとこんな感じの画面になります。
Andriodアプリの登録
droid君のアイコンをタップすることでAndroidアプリの登録をできます。
package名はProject IDと同じです。
アプリを登録を押すとこんな画面になります。
google-service.jsonをダウンロードします。
このファイルはcomposeApp/
に置いてください。
次にFirebase SDKの追加が求められますが、後にFirebase SDKをKMPに対応させたライブラリGitLiveApp/firebase-kotlin-sdkの依存関係の追加とともに記述するので一旦飛ばします。
iOSアプリの登録
同様にしてiOSアプリを登録しようとするとBundle IDが求められます。
Bundle IDはiosApp/Configuration/Config.xcconfig
に書いてあります。
TEAM_ID=
BUNDLE_ID=com.example.firebase.example.FirebaseExample
APP_NAME=FirebaseExample
このBUNDLE_IDをコピペしてください
アプリを登録を押すと設定ファイルのダウンロードが求められます。
これをiosApp/iosApp/
に貼り付けるのですが、ここで必ずXcodeを用いて貼り付けてください。つまりFinderでダウンロードフォルダからGoogleService-Info.plistをXcodeにドラッグして貼り付けて下さい。FinderやAndroid Studio上で移動してもXcodeはそのファイルを認識しません。
XcodeはiosApp/iosApp.xcodeproj
を右クリックして以下のように開くことができます
Xcodeを開いてFinderからファイルをiosApp/iosApp
にドラッグすると以下の画面が出ます
ここでCopy items if needed にチェックを付けて下さい。付けなくても動きはしますが、付けなかった場合XcodeがDownloadフォルダにあるオリジナルのファイルを参照し続けます。つまりDownloadフォルダにあるオリジナルのファイルを消すと動かなくなってしまいます。
貼り付けるとこんな感じになります。
iOS依存関係の設定
Firebaseに戻って次へボタンをクリックするとFirebase SDKの追加が求められます。ここはFirebaseに表示されている指示に従ってXcodeのFile→Add package dependencesを開いて
右上の検索欄にfirebase-ios-sdk
と入力すると
と出るのでfirebase-ios-sdkをクリックして右下のAdd Packagesをクリックします。
こんな感じの画面が出てくるので必要なもののAdd to TargetをiosAppにして右下のAdd Packageをクリックして下さい。今回はStorageのみを利用します。
iOSのFirebase初期化コードの追加
iosApp/iosApp/iosApp.swift
を以下に変更します。
import SwiftUI
import Firebase
@main
struct iOSApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) var delegate
var body: some Scene {
WindowGroup {
ContentView()
}
}
class AppDelegate:NSObject,UIApplicationDelegate{
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
FirebaseApp.configure()
return true
}
}
}
Xcode部分はこれにて終了です。
Firebase Storageの設定
Firebaseから構築→Storageから設定をして下さい。ルールの設定は期限付きで誰でもread可能なルールにしました。また、いらすとやから適当な画像をアップロードしました。
依存関係の追加
Xcodeは閉じてAndroid Studioを開いて下さい
今回は画像の表示ということでCoilも利用します。
(2ファイル書かなければならないのがめんどくさいのでバージョンカタログは利用しません。)
google-serviceのプラグインを追加します。
plugins {
// this is necessary to avoid the plugins to be loaded multiple times
// in each subproject's classloader
alias(libs.plugins.androidApplication) apply false
alias(libs.plugins.androidLibrary) apply false
alias(libs.plugins.jetbrainsCompose) apply false
alias(libs.plugins.compose.compiler) apply false
alias(libs.plugins.kotlinMultiplatform) apply false
+ id("com.google.gms.google-services") version "4.4.2" apply false
}
firebase,coil,ktorを追加します。ktorはcoil3でネットから画像を取得するのに必要だそうです。
(参照: https://coil-kt.github.io/coil/upgrading_to_coil3/ )
import org.jetbrains.compose.desktop.application.dsl.TargetFormat
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
alias(libs.plugins.kotlinMultiplatform)
alias(libs.plugins.androidApplication)
alias(libs.plugins.jetbrainsCompose)
alias(libs.plugins.compose.compiler)
+ id("com.google.gms.google-services")
}
kotlin {
androidTarget {
@OptIn(ExperimentalKotlinGradlePluginApi::class)
compilerOptions {
jvmTarget.set(JvmTarget.JVM_11)
}
}
listOf(
iosX64(),
iosArm64(),
iosSimulatorArm64()
).forEach { iosTarget ->
iosTarget.binaries.framework {
baseName = "ComposeApp"
isStatic = true
}
}
sourceSets {
androidMain.dependencies {
implementation(compose.preview)
implementation(libs.androidx.activity.compose)
+ implementation("io.ktor:ktor-client-okhttp:3.0.0-beta-2")
}
commonMain.dependencies {
implementation(compose.runtime)
implementation(compose.foundation)
implementation(compose.material)
implementation(compose.ui)
implementation(compose.components.resources)
implementation(compose.components.uiToolingPreview)
implementation(libs.androidx.lifecycle.viewmodel)
implementation(libs.androidx.lifecycle.runtime.compose)
+ implementation("dev.gitlive:firebase-common:2.1.0")
+ implementation("dev.gitlive:firebase-storage:2.1.0")
+ implementation("io.ktor:ktor-client-core:3.0.0-beta-2")
+ implementation("io.coil-kt.coil3:coil:3.0.0-alpha10")
+ implementation("io.coil-kt.coil3:coil-compose:3.0.0-alpha10")
+ implementation("io.coil-kt.coil3:coil-network-ktor3:3.0.0-alpha10")
}
+ iosMain.dependencies {
+ implementation("io.ktor:ktor-client-darwin:3.0.0-beta-2")
+ }
}
}
android {
namespace = "com.example.firebase.example"
compileSdk = libs.versions.android.compileSdk.get().toInt()
sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
sourceSets["main"].res.srcDirs("src/androidMain/res")
sourceSets["main"].resources.srcDirs("src/commonMain/resources")
defaultConfig {
applicationId = "com.example.firebase.example"
minSdk = libs.versions.android.minSdk.get().toInt()
targetSdk = libs.versions.android.targetSdk.get().toInt()
versionCode = 1
versionName = "1.0"
}
packaging {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
}
}
buildTypes {
getByName("release") {
isMinifyEnabled = false
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
buildFeatures {
compose = true
}
dependencies {
debugImplementation(compose.uiTooling)
}
}
Compose Multiplatformのコード
Firebaseから画像ダウンロードのURLを取得は以下のようにできます
Firebase.storage.reference.listAll().items.map { it.getDownloadUrl() }
これをAsyncImageに渡せば今回のアプリは完成です。
以下commonMainのApp.ktの全体コードです。
package com.example.firebase.example
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.material.MaterialTheme
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.unit.dp
import coil3.compose.AsyncImage
import dev.gitlive.firebase.Firebase
import dev.gitlive.firebase.storage.storage
import org.jetbrains.compose.ui.tooling.preview.Preview
@Composable
fun App() {
var urls by remember { mutableStateOf(emptyList<String>()) }
LaunchedEffect(Unit){
urls=Firebase.storage.reference.listAll().items.map { it.getDownloadUrl() }
}
MaterialTheme {
LazyVerticalGrid(
columns = GridCells.Adaptive(180.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
modifier = Modifier.fillMaxSize().padding(horizontal = 4.dp),) {
items(urls){
AsyncImage(
it,
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = Modifier.fillMaxWidth()
)
}
}
}
}
これで完成です。(再掲)
Android | iPhone |
---|---|
Discussion