Android Upload File to Cloud Storage for Firebase (Kotlin)

October 24, 2019

Add Firebase to Android (Setup Firebase on Android)

Setup Storage

Goto Firebase Console -> Storage -> Get started.

By default, your rules allow all reads and writes from authenticated users.

// Only authenticated users can read or write to the bucket
service firebase.storage {
  match /b/{bucket}/o {
    match /{allPaths=**} {
      allow read, write: if request.auth != null;
    }
  }
}

If you are not using Firebase Auth yet, you need to make the access public (I believe this is a security risk, and recommended to implement Firebase Auth as soon as possible).

// Anyone can read or write to the bucket, even non-users of your app.
// Because it is shared with Google App Engine, this will also make
// files uploaded via GAE public.
service firebase.storage {
  match /b/{bucket}/o {
    match /{allPaths=**} {
      allow read, write;
    }
  }
}

NOTE: Like Firestore, security rules only apply to client (Android, iOS, Web) while server-side code can access everything.

You might want to read up on storage classes and locations later.

Android

Dependecies

dependencies {
    // implementation 'com.google.firebase:firebase-storage:19.1.0'
    // using kotlin implementation
    implementation 'com.google.firebase:firebase-storage-ktx:19.1.0'
}

Upload

suspend fun uploadPhoto(uid: String?, file: File, name: String, mimeType: String?): String {
    val storage = FirebaseStorage.getInstance()
    val storageRef = storage.reference
    val fileRef = if (uid == null)
        storageRef.child("public/images/$name")
    else
        storageRef.child("user/$uid/images/$name")

    var uri = Uri.fromFile(file)
    // TODO: handle exception
    // await - kotlinx-coroutines-play-services

    val metadata = mimeType?.let {
        StorageMetadata.Builder()
            .setContentType(mimeType)
            // .setCustomMetadata("ref", "test")
            .build()
    }
    val task = if (metadata != null) {
        fileRef.putFile(uri, metadata).await()
    }
    else {
        fileRef.putFile(uri).await()
    }

    // val task = fileRef.putStream(stream).await()

    // task.metadata.reference == fileRef
    val url =  fileRef.downloadUrl.await().toString()

    // url can be used to reconstruct StorageReference
    // val ref = storage.getReferenceFromUrl(url)
    // Timber.d("name=${ref.name}, path=${ref.path}, bucket=${ref.bucket}, same=${fileRef == ref}")

    return url
}

You might want to store the URI path to access the storage object.

val StorageReference.uriString: String
    // gs://bucket/images/stars.jpg
    get() = "gs://$bucket$path"

val id = fileRef.uriString
val ref = storage.getReferenceFromUrl(id)

NOTE: Using Kotlin Coroutines and kotlinx-coroutines-play-services.

Security Rules

rules_version = '2';
service firebase.storage {
  match /b/{bucket}/o {
    // by default, only authenticated user ca read and write
    match /{allPaths=**} {
      allow read, write: if request.auth != null;
    }

    // anyone can read and write at /public
    match /public/{allPaths=**} {
      allow read, write;
    }

    // only owner can read and write at /user/{userId}
    // Files look like: "user/<UID>/path/to/file.txt"
    match /user/{userId}/{allPaths=**} {
      allow read, write: if request.auth.uid == userId;
    }
  }
}

NOTE: You might want to resize image before upload.

References:

This work is licensed under a
Creative Commons Attribution-NonCommercial 4.0 International License.