Skip to content

Instantly share code, notes, and snippets.

@fredy-mederos
Last active June 22, 2018 19:44
Show Gist options
  • Save fredy-mederos/e8a3da3abc8793384327d0867ff9696c to your computer and use it in GitHub Desktop.
Save fredy-mederos/e8a3da3abc8793384327d0867ff9696c to your computer and use it in GitHub Desktop.
RemoteStorageManager with Firebase-Storage
package com.example.remotestorage
import com.google.firebase.storage.FirebaseStorage
import org.koin.dsl.module.applicationContext
/**
* @author @fredy-mederos
*/
val RemoteStorageModule = applicationContext {
// bean { RemoteStorageDemoManagerImpl(true) as RemoteStorageManager }
bean { RemoteStorageManagerImpl(FirebaseStorage.getInstance()) as RemoteStorageManager }
}
package com.example.remotestorage
import android.net.Uri
import com.google.firebase.storage.FirebaseStorage
import kotlinx.coroutines.experimental.delay
import kotlinx.coroutines.experimental.suspendCancellableCoroutine
import java.io.File
/**
* @author @fredy-mederos
*/
/**
* Operation progress [percent], [part] and [total]
*/
typealias OperationProgress = (percent: Int, part: Long, total: Long) -> Unit
interface RemoteStorageManager {
/**
* Upload a [file] to a [remotePath] in remote storage and get notified with [uploadProgress].
* @return an [UploadResult]
*/
suspend fun uploadFile(
remotePath: String,
file: File,
uploadProgress: OperationProgress? = null
): UploadResult
/**
* Download a file from a [remotePath] and save it into given [file]. Also notified with [downloadProgress]
* @return a [DownloadResult]
*/
suspend fun downloadFile(
remotePath: RemotePath,
file: File,
downloadProgress: OperationProgress? = null
): DownloadResult
/**
* Result from [uploadFile] operation
*/
sealed class UploadResult {
/**
* Success response with the [remoteUrl]
*/
data class Success(val remoteUrl: Uri?) : UploadResult()
/**
* Error response with the [ex] Exception
*/
data class Error(val ex: Exception?) : UploadResult()
}
/**
* Result from [downloadFile] operation
*/
sealed class DownloadResult {
/**
* Success response
*/
class Success : DownloadResult()
/**
* Error response with the [ex] Exception
*/
data class Error(val ex: Exception?) : DownloadResult()
}
/**
* Remote [path] param used in [downloadFile] operation
*/
sealed class RemotePath(open val path: String) {
/**
* The remote path is from a child
*/
data class RemoteChild(override val path: String) : RemotePath(path)
/**
* The remote path is from an url
*/
data class RemoteUrl(override val path: String) : RemotePath(path)
}
}
/**
* RemoteStorage implementation using Firebase-Storage
*/
class RemoteStorageManagerImpl(
private val firebaseStorage: FirebaseStorage,
maxUploadRetryTimeMillis: Long = 20 * 1_000 //20 secs for retry upload
) : RemoteStorageManager {
init {
firebaseStorage.maxUploadRetryTimeMillis = maxUploadRetryTimeMillis
}
override suspend fun uploadFile(
remotePath: String,
file: File,
uploadProgress: OperationProgress?
): RemoteStorageManager.UploadResult = suspendCancellableCoroutine { continuation ->
val ref = firebaseStorage.getReference(remotePath)
ref.putFile(Uri.fromFile(file)).addOnSuccessListener { result ->
continuation.resume(RemoteStorageManager.UploadResult.Success(result.downloadUrl))
}.addOnFailureListener { ex ->
continuation.resume(RemoteStorageManager.UploadResult.Error(ex))
}.addOnProgressListener { task ->
uploadProgress?.let {
val percent = ((100 * task.bytesTransferred) / task.totalByteCount).toInt()
it.invoke(percent, task.bytesTransferred, task.totalByteCount)
}
}
}
override suspend fun downloadFile(
remotePath: RemoteStorageManager.RemotePath,
file: File,
downloadProgress: OperationProgress?
): RemoteStorageManager.DownloadResult = suspendCancellableCoroutine { continuation ->
val ref = when (remotePath) {
is RemoteStorageManager.RemotePath.RemoteChild -> firebaseStorage.getReference(
remotePath.path
)
is RemoteStorageManager.RemotePath.RemoteUrl -> firebaseStorage.getReferenceFromUrl(
remotePath.path
)
}
ref.getFile(file).addOnSuccessListener {
continuation.resume(RemoteStorageManager.DownloadResult.Success())
}.addOnFailureListener { ex ->
continuation.resume(RemoteStorageManager.DownloadResult.Error(ex))
}.addOnProgressListener { task ->
downloadProgress?.let {
val percent = ((100f * task.bytesTransferred) / task.totalByteCount).toInt()
it.invoke(percent, task.bytesTransferred, task.totalByteCount)
}
}
}
}
/**
* RemoteStorage implementation for fake demo purposes
*/
class RemoteStorageDemoManagerImpl(private val forceSuccess: Boolean) : RemoteStorageManager {
override suspend fun uploadFile(
remotePath: String,
file: File,
uploadProgress: OperationProgress?
): RemoteStorageManager.UploadResult {
return if (!forceSuccess) {
delay(2_000)
RemoteStorageManager.UploadResult.Error(Exception("Error"))
} else {
(0..10).forEach {
delay(500)
uploadProgress?.invoke(it * 10, it.toLong(), 10L)
}
RemoteStorageManager.UploadResult.Success(Uri.parse("http://fake.external.api/$remotePath"))
}
}
override suspend fun downloadFile(
remotePath: RemoteStorageManager.RemotePath,
file: File,
downloadProgress: OperationProgress?
): RemoteStorageManager.DownloadResult {
return if (!forceSuccess) {
delay(2_000)
RemoteStorageManager.DownloadResult.Error(Exception("Error"))
} else {
(0..10).forEach {
delay(500)
downloadProgress?.invoke(it * 10, it.toLong(), 10L)
}
RemoteStorageManager.DownloadResult.Success()
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment