Download File into app specific storage with Retrofit
In the previous post, DownloadManager is used to download files into the device’s public directory. If the file contains sensitive data, we hope the file can be saved into the app-specific storage rather than public directory. Therefore, we need another method to download. Retrofit is a type-safe HTTP client which help us transfer information and data over a network.
Dependency
Add below dependency into module build.gradle file.
We add codes on the same module of DownloadManager. A button is added next to the DownLoadManager Button. When it is clicked, it will start to download the file using Retrofit.
Implementation
We first create a interface so that Retrofit can translate the java/Kotlin function to HTTP API. The notation @Streaming is used for downloading large file so that it would not write too large data into memory at once.
The download link is https://drive.google.com/uc?export=download&id=1XSTImggtvJ8oZYIB8sEByMuZipiabCaS
The base url is https://drive.google.com/ and it will be set when we create the instance of Retrofit. “uc” is the fixed suffix so we put it inside the parentheses of notation @GET. There is a query for 2 parameters “export” and “id”, which are also seted as the input parameter for the Java/ Kotlin function. Once we know the download id of a file in google drive, we can use this function to download the file. Since the direct download link is used here, the return type of the call is ResponseBody.
interface DownloadFileService {
@Streaming
@GET("uc")
fun downloadFile(@Query("export") export: String, @Query("id") id: String): Call<ResponseBody>
}
object RetrofitServiceCreatorToHomeWebsite { private const val BASE_URL = "https://drive.google.com/" private val retrofit = Retrofit.Builder() .baseUrl(BASE_URL) //.addConverterFactory(GsonConverterFactory.create()) .build() fun <T> create(serviceClass: Class<T>): T = retrofit.create(serviceClass) }
val retrofitDownloadbutton: Button = findViewById(R.id.retrofitDownloadButton) retrofitDownloadbutton.setOnClickListener { Log.d(TAG, "click Retrofit download button") startDownloadWithRetrofit() }
In this function, the object responsible for HTTP API is retrieved. The query parameter is entered. The access request is enqueue for connection with a callback. In callback, The location to save the file is specified here and is passed to a function with response.body(). Remember to invoke the function inside coroutine to avoid blocking the main thread.
fun startDownloadWithRetrofit(){ val downloadFileService: DownloadFileService = RetrofitServiceCreatorToHomeWebsite.create(DownloadFileService::class.java) val call = downloadFileService.downloadFile(downloadLineExport, downloadLinkId) call.enqueue(object: Callback<ResponseBody> { override fun onResponse(call: Call<ResponseBody>, response: Response<ResponseBody>){ if(response.isSuccessful){ Log.d(TAG, "The file is found on server") //indicate the download location val customDirectory = File(applicationContext.cacheDir, "custom") if( !customDirectory.exists()) customDirectory.mkdir() val filePath = File(customDirectory, fileName) networkRequestScope.launch { writeResponseBodyToStorage(response.body()!!, filePath) Log.d(TAG, "end download. File is saved on ${filePath.absolutePath}") } Log.d(TAG, "Callback.onResponse end") }else{ Log.e(TAG, "fail to connect server: " + response.toString()) } } override fun onFailure(call: Call<ResponseBody>, t: Throwable) { Log.e(TAG, "Request Download Error", t) } }) }
suspend fun writeResponseBodyToStorage(body: ResponseBody, path: File){ var inputStream: InputStream? = null var outputStream: OutputStream? = null val fileSize = body.contentLength() var byteDownloaded = 0 Log.d(TAG, "file will save to ${path.absoluteFile} with size $fileSize") try { inputStream = body.byteStream() outputStream = FileOutputStream(path) val buffer = ByteArray(4 * 1024) //allocate 4k Byte buffer while (true) { val byteRead = inputStream.read(buffer) if (byteRead < 0) break outputStream.write(buffer, 0, byteRead) byteDownloaded += byteRead Log.d(TAG, "download progress $byteDownloaded of $fileSize") } outputStream.flush() } catch (ex: IOException) { Log.e(TAG, "fail to save file", ex) } finally { inputStream?.close() outputStream?.close() } }
Logcat shows the download progress and the path of the download file in the device
留言
發佈留言