In order to work in all versions in AndroidManifest.xml I have added:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" /> <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />Please notice if you use MANAGE_EXTERNAL_STORAGE most probably you app will be rejected on play store To check storage permission for all versions of Android I have used following code:
@RequiresApi(Build.VERSION_CODES.R) fun checkLocalStoragePermission() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { if (!Environment.isExternalStorageManager()) { val uri = Uri.parse("package:${BuildConfig.APPLICATION_ID}") startActivity( Intent( Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION, uri ) ) } } }In app\build.gradle.kts I have added buildConfig = true,
buildFeatures { buildConfig = true }so that piece of code
val uri = Uri.parse("package:${BuildConfig.APPLICATION_ID}")works
Also, for retrofit I will need permission:
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />In app\build.gradle.kts I have added retrofit:
implementation( "com.squareup.retrofit2:retrofit:2.9.0") implementation("com.squareup.okhttp3:okhttp:4.12.0") implementation("com.squareup.retrofit2:converter-scalars:2.9.0") implementation("com.squareup.retrofit2:converter-gson:2.9.0")Then I have added WebApiService interface:
import okhttp3.MultipartBody import retrofit2.Call import retrofit2.http.Multipart import retrofit2.http.POST import retrofit2.http.Part interface WebApiService { @Multipart @POST("api/UploadPictures/UploadImage") fun uploadImage( @Part image: MultipartBody.Part? ): Call<UploadResponse> } data class UploadResponse( @SerializedName("message") val message: String )Retrofit is almost the same as I already explained here, except the post method:
val imageFile = File(imagePath) val requestBody = imageFile.asRequestBody("image/*".toMediaTypeOrNull()) val imagePart = MultipartBody.Part.createFormData("image", imageFile.name, requestBody) val apiService = retrofit.create(WebApiService::class.java) val webApiRequest = apiService.uploadImage(imagePart)Here how the whole file looks like:
import android.app.AlertDialog import android.util.Log import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.MultipartBody import okhttp3.OkHttpClient import okhttp3.RequestBody.Companion.asRequestBody import retrofit2.Call import retrofit2.Callback import retrofit2.Response import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory import java.io.File import java.security.cert.X509Certificate import javax.net.ssl.SSLContext import javax.net.ssl.TrustManager import javax.net.ssl.X509TrustManager class UploadImageRetrofit { fun uploadImage(imagePath: String, alertDialogBuilder: AlertDialog.Builder): String? { 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 val client = OkHttpClient.Builder() .sslSocketFactory(sslSocketFactory, trustAllCerts[0] as X509TrustManager) .hostnameVerifier { _, _ -> true }.build() val retrofit = Retrofit.Builder() .baseUrl("https://10.0.2.2:7181/") .client(client) .addConverterFactory(GsonConverterFactory.create()) .build() val imageFile = File(imagePath) val requestBody = imageFile.asRequestBody("image/*".toMediaTypeOrNull()) val imagePart = MultipartBody.Part.createFormData("image", imageFile.name, requestBody) val apiService = retrofit.create(WebApiService::class.java) val webApiRequest = apiService.uploadImage(imagePart) webApiRequest.enqueue(object : Callback<UploadResponse> { 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() } }) return null } }MainActivity.kt:
import android.app.AlertDialog import android.content.Intent import android.net.Uri import android.os.Build import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import android.os.Environment import android.provider.Settings import android.view.View import androidx.annotation.RequiresApi class MainActivity : AppCompatActivity() { @RequiresApi(Build.VERSION_CODES.R) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) checkLocalStoragePermission() } fun onUploadImageButtonClick(view: View) { val uploadImageRetrofit = UploadImageRetrofit() val alertDialogBuilder = AlertDialog.Builder(this@MainActivity) uploadImageRetrofit.uploadImage("/sdcard/Download/IMG_20240120_133805.jpg", alertDialogBuilder) } @RequiresApi(Build.VERSION_CODES.R) fun checkLocalStoragePermission() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { if (!Environment.isExternalStorageManager()) { val uri = Uri.parse("package:${BuildConfig.APPLICATION_ID}") startActivity( Intent( Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION, uri ) ) } } } }Download from here --- Web API .Net Core controller:
using Microsoft.AspNetCore.Mvc; namespace UploadPictures.Controllers; [ApiController] [Route("api/[controller]")] public class UploadPicturesController : Controller { private readonly string _uploadPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "UploadPictures", "uploads"); [HttpPost] [Route("UploadImage")] public async Task<IActionResult> UploadImage() { try { if (!Request.HasFormContentType) { return BadRequest("Invalid content type. Must be multipart/form-data."); } var form = await Request.ReadFormAsync(); var file = form.Files.FirstOrDefault(); if (file == null) { return BadRequest("No image file found in the request."); } // Generate a unique filename var filename = Path.GetRandomFileName() + Path.GetExtension(file.FileName); var filePath = Path.Combine(_uploadPath, filename); // Create the upload directory if it doesn't exist Directory.CreateDirectory(_uploadPath); // Save the uploaded file await using (var stream = new FileStream(filePath, FileMode.Create)) { await file.CopyToAsync(stream); } return Ok(new { message = "Image uploaded successfully." }); } catch (Exception ex) { // Log the error for debugging Console.WriteLine(ex.ToString()); return StatusCode(500, "Internal server error."); } } }Download Visual Studio .NET Core example from here