In this example I will upload pictures directly from builtIn gallery using
this example.
Prepare virtual device like I already explained
here.
Controller looks same as
here.
In AndroidManifest.xml I need only retrofit permissions:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
Api service:
import com.google.gson.annotations.SerializedName
import okhttp3.MultipartBody
import retrofit2.Call
import retrofit2.http.Multipart
import retrofit2.http.POST
import retrofit2.http.Part
interface IWebApiService {
@Multipart
@POST("api/UploadPictures/UploadImage")
fun uploadImage(
@Part image: MultipartBody.Part?
): Call<UploadResponse>
}
data class UploadResponse(
@SerializedName("message")
val message: String
)
This time I will create Retrofit using dependency injection:
import android.util.Log
import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import java.security.cert.X509Certificate
import javax.net.ssl.SSLContext
import javax.net.ssl.TrustManager
import javax.net.ssl.X509TrustManager
internal class CreateRetrofitBuilder : ICreateRetrofitBuilder {
override fun createRetrofitBuilder(baseUrl: String): Retrofit {
return Retrofit.Builder()
.baseUrl(baseUrl)
.client(trustAllCertificates())
.addConverterFactory(GsonConverterFactory.create())
.build()
}
private fun trustAllCertificates(): OkHttpClient {
val trustAllCerts = arrayOf<TrustManager>(object : X509TrustManager {
override fun checkClientTrusted(chain: Array<out X509Certificate>?, authType: String?) {
Log.i(MainActivity::class.simpleName, "checkClientTrusted")
}
override fun checkServerTrusted(chain: Array<out X509Certificate>?, authType: String?) {
Log.i(MainActivity::class.simpleName, "checkServerTrusted")
}
override fun getAcceptedIssuers() = arrayOf<X509Certificate>()
})
val sslContext = SSLContext.getInstance("SSL")
sslContext.init(null, trustAllCerts, java.security.SecureRandom())
// Create an ssl socket factory with our all-trusting manager
val sslSocketFactory = sslContext.socketFactory
// connect to server
return OkHttpClient.Builder()
.sslSocketFactory(sslSocketFactory, trustAllCerts[0] as X509TrustManager)
.hostnameVerifier { _, _ -> true }.build()
}
}
Here notice that I am using "GsonConverterFactory" which means that Retrofit expects JSON anwer from the server, which is why I need UploadResponse class, and from server I will respond with:
return Ok(new { message = "Image uploaded successfully." });
Otherwise I would receive error like:
com.google.gson.stream.MalformedJsonException: Use JsonReader.setLenient(true) to accept malformed JSON at line 1 column 1 path $
Where ICreateRetrofitBuilder looks like:
import retrofit2.Retrofit
interface ICreateRetrofitBuilder {
fun createRetrofitBuilder(baseUrl: String): Retrofit
}
onResponse and onFailure I will also inject and use the like call backs:
import android.app.AlertDialog
import retrofit2.Call
import retrofit2.Response
class UploadImageRetrofitCallBacks(private val alertDialogBuilder: AlertDialog.Builder) :
IUploadImageRetrofitCallBacks {
override fun onResponse(call: Call<UploadResponse>, response: Response<UploadResponse>) {
if (!response.isSuccessful) {
alertDialogBuilder.setMessage(response.errorBody()!!.charStream().readText())
.setCancelable(false)
.setNeutralButton("OK") { dialog, _ ->
dialog.dismiss()
}
val alert = alertDialogBuilder.create()
alert.setTitle("Error")
alert.show()
} else {
alertDialogBuilder.setMessage("Response: ${response.body()?.message.toString()}")
.setCancelable(false)
.setNeutralButton("OK") { dialog, _ ->
dialog.dismiss()
}
val alert = alertDialogBuilder.create()
alert.setTitle("Success")
alert.show()
}
}
override fun onFailure(call: Call<UploadResponse>, t: Throwable) {
alertDialogBuilder.setMessage(t.message)
.setCancelable(false)
.setNeutralButton("OK") { dialog, _ ->
dialog.dismiss()
}
val alert = alertDialogBuilder.create()
alert.setTitle("Error")
alert.show()
}
}
Where interface looks like:
import retrofit2.Call
import retrofit2.Response
interface IUploadImageRetrofitCallBacks {
fun onResponse(call: Call<UploadResponse>, response: Response<UploadResponse>)
fun onFailure(call: Call<UploadResponse>, t: Throwable)
}
Upload image:
import android.content.Context
import android.net.Uri
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.MultipartBody
import okhttp3.RequestBody.Companion.toRequestBody
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
class UploadImageRetrofit(private val uploadImageRetrofitCallBacks: IUploadImageRetrofitCallBacks, private val webApiService: IWebApiService) {
fun uploadImage(imgUri: Uri, context: Context): String? {
val inputStream = context.contentResolver.openInputStream(imgUri)
val mediaType = context.contentResolver.getType(imgUri)?.toMediaTypeOrNull()
val requestFile = inputStream?.use {
it.readBytes().toRequestBody(mediaType)
}
val imagePart: MultipartBody.Part? = requestFile?.let {
MultipartBody.Part.createFormData("image", "image.jpg", it)
}
val webApiRequest = webApiService.uploadImage(imagePart)
webApiRequest.enqueue(object : Callback<UploadResponse> {
override fun onResponse(call: Call<UploadResponse>, response: Response<UploadResponse>) {
uploadImageRetrofitCallBacks.onResponse(call, response)
}
override fun onFailure(call: Call<UploadResponse>, t: Throwable) {
uploadImageRetrofitCallBacks.onFailure(call, t)
}
})
return null
}
}
At the end, MainActivity looks like this:
import android.app.AlertDialog
import android.os.Bundle
import android.view.View
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
private lateinit var uploadImage: UploadImageRetrofit
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
uploadImage = UploadImageRetrofit(
UploadImageRetrofitCallBacks(AlertDialog.Builder(this@MainActivity)),
CreateRetrofitBuilder().createRetrofitBuilder("https://10.0.2.2:7181/")
.create(IWebApiService::class.java)
)
}
private val galleryLauncher =
this.registerForActivityResult(ActivityResultContracts.GetMultipleContents()) { images ->
images.forEach { imgUri ->
uploadImage.uploadImage(imgUri, this)
}
}
fun onOpenGalleryAndUploadButtonClick(view: View) {
galleryLauncher.launch("image/*")
}
}
Download from
here.