Skip to content

Commit 485cf30

Browse files
author
cramsan
committed
Adding firebase storage client
1 parent 45bb2e9 commit 485cf30

File tree

8 files changed

+333
-13
lines changed

8 files changed

+333
-13
lines changed

build.gradle.kts

+65-2
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ val jar by tasks.getting(Jar::class) {
8181
it.path.startsWith("${projectDir.path}${File.separator}build${File.separator}jar")
8282
}.map { zipTree(it) }
8383
})
84+
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
8485
}
8586

8687
val sourceSets = project.the<SourceSetContainer>()
@@ -143,20 +144,82 @@ publishing {
143144
}
144145
}
145146

147+
/**
148+
* List of aar files to include in the jar. Some jars are being omitted because they are not needed for the JVM.
149+
* - lifecycle-*: exclude lifecycle libs due to https://github.com/GitLiveApp/firebase-java-sdk/pull/15 - remove the exclude once the dependencies in the aars are updated to the required version
150+
* - savedstate: Excluded due to this library already being included as part of the compose mutliplatform dependencies. It does not seem to be directly needed by the firebase libraries.
151+
*/
152+
val includeList = listOf(
153+
"activity-*.jar",
154+
"asynclayoutinflater-*.jar",
155+
"coordinatorlayout-*.jar",
156+
"core-*.jar",
157+
"core-runtime-*.jar",
158+
"cursoradapter-*.jar",
159+
"customview-*.jar",
160+
"documentfile-*.jar",
161+
"drawerlayout-*.jar",
162+
"firebase-abt-*.jar",
163+
"firebase-appcheck-*.jar",
164+
"firebase-appcheck-interop-*.jar",
165+
"firebase-auth-interop-*.jar",
166+
"firebase-common-*.jar",
167+
"firebase-common-*.jar",
168+
"firebase-common-ktx-*.jar",
169+
"firebase-common-ktx-*.jar",
170+
"firebase-components-*.jar",
171+
"firebase-components-*.jar",
172+
"firebase-config-*.jar",
173+
"firebase-config-interop-*.jar",
174+
"firebase-database-*.jar",
175+
"firebase-database-collection-*.jar",
176+
"firebase-encoders-json-*.jar",
177+
"firebase-firestore-*.jar",
178+
"firebase-functions-*.jar",
179+
"firebase-iid-*.jar",
180+
"firebase-iid-interop-*.jar",
181+
"firebase-installations-*.jar",
182+
"firebase-installations-interop-*.jar",
183+
"firebase-measurement-connector-*.jar",
184+
"firebase-storage-*.jar",
185+
"fragment-*.jar",
186+
"fragment-*.jar",
187+
"grpc-android-*.jar",
188+
"interpolator-*.jar",
189+
"legacy-support-core-ui-*.jar",
190+
"legacy-support-core-utils-*.jar",
191+
"loader-*.jar",
192+
"localbroadcastmanager-*.jar",
193+
"play-services-base-*.jar",
194+
"play-services-basement-*.jar",
195+
"play-services-basement-*.jar",
196+
"play-services-cloud-messaging-*.jar",
197+
"play-services-stats-*.jar",
198+
"play-services-tasks-*.jar",
199+
"play-services-tasks-*.jar",
200+
"print-*.jar",
201+
"protolite-well-known-types-*.jar",
202+
"slidingpanelayout-*.jar",
203+
"swiperefreshlayout-*.jar",
204+
"versionedparcelable-*.jar",
205+
"viewpager-*.jar",
206+
)
207+
146208
dependencies {
147209
compileOnly("org.robolectric:android-all:12.1-robolectric-8229987")
148210
testImplementation("junit:junit:4.13.2")
149211
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-swing:1.7.3")
150212
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.7.3")
213+
testImplementation("org.mockito:mockito-core:5.12.0")
151214
// firebase aars
152215
aar("com.google.firebase:firebase-firestore:24.10.0")
153216
aar("com.google.firebase:firebase-functions:20.4.0")
154217
aar("com.google.firebase:firebase-database:20.3.0")
155218
aar("com.google.firebase:firebase-config:21.6.0")
156219
aar("com.google.firebase:firebase-installations:17.2.0")
220+
aar("com.google.firebase:firebase-storage:21.0.0")
157221
// extracted aar dependencies
158-
// exclude lifecycle libs due to https://github.com/GitLiveApp/firebase-java-sdk/pull/15 - remove the exclude once the dependencies in the aars are updated to the required version
159-
api(fileTree(mapOf("dir" to "build/jar", "include" to listOf("*.jar"), "exclude" to listOf("lifecycle-*"))))
222+
api(fileTree(mapOf("dir" to "build/jar", "include" to includeList)))
160223
// polyfill dependencies
161224
implementation("org.jetbrains.kotlin:kotlin-stdlib")
162225
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")

src/main/java/android/content/pm/PackageManager.java

+1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ public ServiceInfo getServiceInfo(ComponentName component, int flags) throws Nam
3939
data.put("com.google.firebase.components:com.google.firebase.functions.FunctionsRegistrar", "com.google.firebase.components.ComponentRegistrar");
4040
data.put("com.google.firebase.components:com.google.firebase.installations.FirebaseInstallationsRegistrar", "com.google.firebase.components.ComponentRegistrar");
4141
data.put("com.google.firebase.components:com.google.firebase.iid.Registrar", "com.google.firebase.components.ComponentRegistrar");
42+
data.put("com.google.firebase.components:com.google.firebase.storage.StorageRegistrar", "com.google.firebase.components.ComponentRegistrar");
4243
return new ServiceInfo(data);
4344
}
4445
throw new IllegalArgumentException(component.cls);

src/main/java/android/net/ConnectivityManager.kt

+4
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ class ConnectivityManager private constructor() {
2121
connected.removeEventListener(networkCallback)
2222
}
2323

24+
fun getActiveNetworkInfo(): NetworkInfo {
25+
return NetworkInfo()
26+
}
27+
2428
open class NetworkCallback : ValueEventListener {
2529
override fun onDataChange(data: DataSnapshot) {
2630
when (data.getValue(Boolean::class.java)) {
+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package android.net
2+
3+
class NetworkInfo {
4+
var type: Int = 1 // ConnectivityManager.TYPE_WIFI
5+
val isConnectedOrConnecting: Boolean = true
6+
val isConnected: Boolean = true
7+
val isSuspended: Boolean = false
8+
val isAvailable: Boolean = true
9+
}

src/main/java/android/net/Uri.kt

+159-3
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,45 @@
11
package android.net
22

33
import java.net.URI
4+
import java.net.URLDecoder
5+
import java.net.URLEncoder
6+
import java.nio.charset.StandardCharsets
7+
import java.nio.file.Paths
48
import java.util.Collections
59

610
class Uri(private val uri: URI) {
711

812
companion object {
913
@JvmStatic
1014
fun parse(uriString: String) = Uri(URI.create(uriString))
15+
16+
@JvmStatic
17+
fun encode(s: String?): String? = encode(s, null)
18+
19+
@JvmStatic
20+
fun encode(s: String?, allow: String?): String? {
21+
return URLEncoder.encode(s, StandardCharsets.UTF_8)
22+
}
23+
24+
@JvmStatic
25+
fun decode(s: String?): String? {
26+
return URLDecoder.decode(s, StandardCharsets.UTF_8)
27+
}
1128
}
1229

13-
val scheme get() = uri.scheme
14-
val port get() = uri.port
15-
val host get() = uri.host
30+
fun getScheme(): String = uri.scheme
31+
32+
fun getPort(): Int = uri.port
33+
34+
fun getHost(): String = uri.host
35+
36+
fun getPath(): String = uri.path
37+
38+
fun getAuthority(): String = uri.authority
39+
40+
fun getQuery() = uri.query
41+
42+
fun getFragment() = uri.fragment
1643

1744
fun getQueryParameterNames(): Set<String> {
1845
val query: String = uri.query ?: return emptySet()
@@ -65,4 +92,133 @@ class Uri(private val uri: URI) {
6592
} while (true)
6693
return null
6794
}
95+
96+
fun buildUpon(): Builder {
97+
return Builder()
98+
.scheme(this.getScheme())
99+
.authority(this.getAuthority())
100+
.path(this.getPath())
101+
.query(this.getQuery())
102+
.fragment(this.getFragment())
103+
}
104+
105+
override fun toString(): String {
106+
return uri.toString()
107+
}
108+
109+
class Builder {
110+
private var scheme: String? = null
111+
private var opaquePart: String? = null
112+
private var authority: String? = null
113+
private var path: String? = null
114+
private var query: String? = null
115+
private var fragment: String? = null
116+
117+
fun scheme(scheme: String?): Builder {
118+
this.scheme = scheme
119+
return this
120+
}
121+
122+
fun opaquePart(opaquePart: String?): Builder {
123+
this.opaquePart = opaquePart
124+
return this
125+
}
126+
127+
fun encodedOpaquePart(opaquePart: String?): Builder {
128+
return opaquePart(URLDecoder.decode(opaquePart, StandardCharsets.UTF_8.toString()))
129+
}
130+
131+
fun authority(authority: String?): Builder {
132+
this.opaquePart = null
133+
this.authority = authority
134+
return this
135+
}
136+
137+
fun encodedAuthority(authority: String?): Builder {
138+
return authority(URLDecoder.decode(authority, StandardCharsets.UTF_8.toString()))
139+
}
140+
141+
fun path(path: String?): Builder {
142+
this.opaquePart = null
143+
this.path = path
144+
return this
145+
}
146+
147+
fun encodedPath(path: String?): Builder {
148+
return this.path(URLDecoder.decode(path, StandardCharsets.UTF_8.toString()))
149+
}
150+
151+
fun appendPath(newSegment: String?): Builder {
152+
val createdPath = Paths.get(this.path.orEmpty(), newSegment).toString()
153+
return this.path(createdPath)
154+
}
155+
156+
fun appendEncodedPath(newSegment: String?): Builder {
157+
val newDecodedSegment = URLDecoder.decode(newSegment, StandardCharsets.UTF_8.toString())
158+
return appendPath(newDecodedSegment)
159+
}
160+
161+
fun query(query: String?): Builder {
162+
this.opaquePart = null
163+
this.query = query
164+
return this
165+
}
166+
167+
fun encodedQuery(query: String?): Builder {
168+
return this.query(URLDecoder.decode(query, StandardCharsets.UTF_8.toString()))
169+
}
170+
171+
fun fragment(fragment: String?): Builder {
172+
this.fragment = fragment
173+
return this
174+
}
175+
176+
fun encodedFragment(fragment: String?): Builder {
177+
return this.query(URLDecoder.decode(fragment, StandardCharsets.UTF_8.toString()))
178+
}
179+
180+
fun appendQueryParameter(key: String?, value: String?): Builder {
181+
this.opaquePart = null
182+
val encodedParameter = encode(key) + "=" + encode(value)
183+
if (this.query == null) {
184+
this.query = decode(encodedParameter)
185+
return this
186+
} else {
187+
val oldQuery: String = encode(query)!!
188+
if (oldQuery.isNotEmpty()) {
189+
this.query = decode("$oldQuery&$encodedParameter")
190+
} else {
191+
this.query = decode(encodedParameter)
192+
}
193+
194+
return this
195+
}
196+
}
197+
198+
fun clearQuery(): Builder {
199+
return this.query(null)
200+
}
201+
202+
fun build(): Uri {
203+
return if (this.opaquePart != null) {
204+
if (this.scheme == null) {
205+
throw UnsupportedOperationException("An opaque URI must have a scheme.")
206+
} else {
207+
Uri(URI(this.scheme, this.opaquePart, this.fragment))
208+
}
209+
} else {
210+
Uri(URI(this.scheme, this.authority, path, this.query, this.fragment))
211+
}
212+
}
213+
214+
override fun toString(): String {
215+
return this.build().toString()
216+
}
217+
}
218+
68219
}
220+
221+
/** Index of a component which was not found. */
222+
private const val NOT_FOUND = -1
223+
224+
private val HEX_DIGITS = "0123456789ABCDEF".toCharArray()
+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import android.app.Application
2+
import android.content.Context
3+
import android.net.ConnectivityManager
4+
import android.net.Uri
5+
import com.google.firebase.Firebase
6+
import com.google.firebase.FirebaseApp
7+
import com.google.firebase.FirebaseOptions
8+
import com.google.firebase.FirebasePlatform
9+
import com.google.firebase.initialize
10+
import com.google.firebase.storage.internal.Slashes
11+
import com.google.firebase.storage.storage
12+
import fakes.FakeFirebasePlatform
13+
import org.junit.Assert
14+
import org.junit.Before
15+
import org.junit.Test
16+
17+
class FirestoreStorageTest : FirebaseTest() {
18+
19+
private lateinit var app: FirebaseApp
20+
21+
@Before
22+
fun initialize() {
23+
FirebasePlatform.initializeFirebasePlatform(FakeFirebasePlatform())
24+
val options = FirebaseOptions
25+
.Builder()
26+
.setProjectId("my-firebase-project")
27+
.setApplicationId("1:27992087142:android:ce3b6448250083d1")
28+
.setApiKey("AIzaSyADUe90ULnQDuGShD9W23RDP0xmeDc6Mvw")
29+
.setStorageBucket("fir-kotlin-sdk.appspot.com")
30+
// setDatabaseURL(...)
31+
.build()
32+
app = Firebase.initialize(Application(), options)
33+
}
34+
35+
@Test
36+
fun test_parsing_storage_uri() {
37+
val input = "gs://edifikana-stage.appspot.com"
38+
39+
val normalized = Slashes.normalizeSlashes(input.substring(5))
40+
val fullUri = Slashes.preserveSlashEncode(normalized)
41+
val parsedUri = Uri.parse("gs://$fullUri");
42+
43+
Assert.assertEquals("gs://edifikana-stage.appspot.com", parsedUri.toString())
44+
}
45+
46+
@Test
47+
fun test_loading_default_storage_client() {
48+
Firebase.storage
49+
}
50+
51+
@Test
52+
fun test_getting_root_reference() {
53+
val storage = Firebase.storage
54+
val reference = storage.reference
55+
Assert.assertNotNull(reference)
56+
}
57+
58+
@Test
59+
fun test_getting_child_reference() {
60+
val storage = Firebase.storage
61+
val reference = storage.reference
62+
val downloadRef = reference.child("mountains.jpg")
63+
val downloadUrl = downloadRef.downloadUrl
64+
65+
Assert.assertNotNull(downloadUrl)
66+
}
67+
}

src/test/kotlin/FirestoreTest.kt

+2-8
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import com.google.firebase.FirebaseOptions
44
import com.google.firebase.FirebasePlatform
55
import com.google.firebase.firestore.firestore
66
import com.google.firebase.initialize
7+
import fakes.FakeFirebasePlatform
78
import kotlinx.coroutines.runBlocking
89
import kotlinx.coroutines.tasks.await
910
import org.junit.Assert.assertEquals
@@ -14,14 +15,7 @@ import java.io.File
1415
class FirestoreTest : FirebaseTest() {
1516
@Before
1617
fun initialize() {
17-
FirebasePlatform.initializeFirebasePlatform(object : FirebasePlatform() {
18-
val storage = mutableMapOf<String, String>()
19-
override fun store(key: String, value: String) = storage.set(key, value)
20-
override fun retrieve(key: String) = storage[key]
21-
override fun clear(key: String) { storage.remove(key) }
22-
override fun log(msg: String) = println(msg)
23-
override fun getDatabasePath(name: String) = File("./build/$name")
24-
})
18+
FirebasePlatform.initializeFirebasePlatform(FakeFirebasePlatform())
2519
val options = FirebaseOptions.Builder()
2620
.setProjectId("my-firebase-project")
2721
.setApplicationId("1:27992087142:android:ce3b6448250083d1")

0 commit comments

Comments
 (0)