I made a Flask API which can take an image as input as Base64 string. Now I want to send request on that API from Android app using Ktor. I am using Jetpack Compose for UI here.
I succeeded to send request to that api but there is a problem, the Base64 string is going to the api as empty string. Now to convert the image to base64, I am using this code.
if (imageUri.value!=null){
val file = File(imageUri.value.toString()).readBytes()
imageStr = Base64.encodeToString(file, Base64.DEFAULT)
println(imageStr)
}
Is this code correct? I want to pick an image from gallery and convert that to base64 string. I am also giving the code of the MainActivity.kt and my Composable function file code.
MainActivity.kt
class MainActivity : ComponentActivity() {
private var imageUri = mutableStateOf<Uri?>(null)
private var imageStr: String = ""
private var post: ImageResponse? = null
private var cropActivityResultContracts = object : ActivityResultContract<Any?, Uri?>() {
override fun createIntent(context: Context, input: Any?): Intent {
return CropImage.activity()
.getIntent(this@MainActivity)
}
override fun parseResult(resultCode: Int, intent: Intent?): Uri? {
return CropImage.getActivityResult(intent)?.uri
}
}
private val launcher = registerForActivityResult(cropActivityResultContracts) { uri ->
imageUri.value = uri
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (imageUri.value!=null){
val file = File(imageUri.value.toString()).readBytes()
imageStr = Base64.encodeToString(file, Base64.DEFAULT)
println(imageStr)
}
setContent {
val scope = rememberCoroutineScope()
UploadImageTheme {
ImagePickerDesign(
onSelect = {
launcher.launch("image/*")
},
imageUri = imageUri,
onUpload = {
scope.launch {
post = service.postImage(
ImageRequest(imageStr)
)
}
},
result = post.pred_percentage
)
}
}
}
}
And one more thing! How can I display the response from the server in the app? In the above code, result parameter will take take the response from the server and display it. But for some reason I can't update that value in the UI.
ImagePickerDesign.kt
package com.example.uploadimage
import android.net.Uri
import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.unit.dp
import com.skydoves.landscapist.glide.GlideImage
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.sp
@Composable
fun ImagePickerDesign(
imageUri: MutableState<Uri?>,
onUpload: () -> Unit,
onSelect: () -> Unit,
result: String
) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
verticalArrangement = Arrangement.SpaceEvenly,
horizontalAlignment = Alignment.CenterHorizontally
) {
if (imageUri.value != null) {
GlideImage(
imageModel = imageUri.value,
modifier = Modifier
.size(350.dp)
.clickable(onClick = onSelect),
contentScale = ContentScale.Fit
)
}else{
Image(
painter = painterResource(id = R.drawable.placeholder_image),
contentDescription = "placeholder",
modifier = Modifier
.size(350.dp)
.clickable(onClick = onSelect),
colorFilter = ColorFilter.tint(Color.Black)
)
}
Text(text = result, fontSize = 16.sp)
Button(onClick = onUpload) {
Text(text = "Upload Image")
}
}
}
You can also see my project here: https://github.com/srang992/Pancard-Tampering-app
This is the output while running the Android app:
Install successfully finished in 6 s 709 ms.
$ adb shell am start -n "com.example.uploadimage/com.example.uploadimage.MainActivity" -a android.intent.action.MAIN -c android.intent.category.LAUNCHER
Connected to process 32033 on device 'xiaomi-m2010j19si-6686ff050321'.
Capturing and displaying logcat messages from application. This behavior can be disabled in the "Logcat output" section of the "Debugger" settings page.
I/ple.uploadimag: Late-enabling -Xcheck:jni
E/ple.uploadimag: Unknown bits set in runtime_flags: 0x48000
I/Perf: Connecting to perf service.
I/FeatureParser: can't find lime.xml in assets/device_features/,it may be in /vendor/etc/device_features
E/libc: Access denied finding property "ro.vendor.df.effect.conflict"
E/libc: Access denied finding property "ro.vendor.knock.type"
W/ple.uploadimage: type=1400 audit(0.0:18925): avc: denied { read } for name="u:object_r:vendor_default_prop:s0" dev="tmpfs" ino=16328 scontext=u:r:untrusted_app:s0:c35,c257,c512,c768 tcontext=u:object_r:vendor_default_prop:s0 tclass=file permissive=0
type=1400 audit(0.0:18926): avc: denied { read } for name="u:object_r:vendor_default_prop:s0" dev="tmpfs" ino=16328 scontext=u:r:untrusted_app:s0:c35,c257,c512,c768 tcontext=u:object_r:vendor_default_prop:s0 tclass=file permissive=0
W/Looper: PerfMonitor looperActivity : package=com.example.uploadimage/.MainActivity time=273ms latency=938ms running=224ms procState=2 ClientTransaction{ callbacks=[android.app.servertransaction.LaunchActivityItem] lifecycleRequest=android.app.servertransaction.ResumeActivityItem } historyMsgCount=1 (msgIndex=1 wall=997ms seq=2 running=873ms runnable=46ms io=17ms late=2ms h=android.app.ActivityThread$H w=110)
W/Looper: PerfMonitor looperActivity : package=com.example.uploadimage/.MainActivity time=0ms latency=1216ms running=0ms procState=2 ClientTransaction{ callbacks=[android.app.servertransaction.TopResumedActivityChangeItem] } historyMsgCount=2 (msgIndex=1 wall=997ms seq=2 running=873ms runnable=46ms io=17ms late=2ms h=android.app.ActivityThread$H w=110) (msgIndex=2 wall=273ms seq=3 running=224ms runnable=4ms io=20ms late=938ms h=android.app.ActivityThread$H w=159)
I/AdrenoGLES-0: QUALCOMM build : 243b6bc, I98aee2b40e
Build Date : 12/08/20
OpenGL ES Shader Compiler Version: EV031.29.00.16
Local Branch :
Remote Branch : refs/tags/AU_LINUX_ANDROID_LA.UM.8.15.R1.10.00.00.643.066
Remote Branch : NONE
Reconstruct Branch : NOTHING
Build Config : S P 8.0.16 AArch64
Driver Path : /vendor/lib64/egl/libGLESv2_adreno.so
I/AdrenoGLES-0: PFP: 0x016ee188, ME: 0x00000000
I/OpenGLRenderer: Davey! duration=902ms; Flags=1, IntendedVsync=17962622634853, Vsync=17962905968175, OldestInputEvent=9223372036854775807, NewestInputEvent=0, HandleInputStart=17962922546739, AnimationStart=17962922639291, PerformTraversalsStart=17962922843354, DrawStart=17963416902364, SyncQueued=17963440580541, SyncStart=17963440964031, IssueDrawCommandsStart=17963441058406, SwapBuffers=17963524637156, FrameCompleted=17963525914447, DequeueBufferDuration=106000, QueueBufferDuration=392000,
W/Looper: PerfMonitor doFrame : time=605ms vsyncFrame=646397 latency=300ms procState=2 historyMsgCount=4 (msgIndex=1 wall=997ms seq=2 running=873ms runnable=46ms io=17ms late=2ms h=android.app.ActivityThread$H w=110) (msgIndex=2 wall=273ms seq=3 running=224ms runnable=4ms io=20ms late=938ms h=android.app.ActivityThread$H w=159)
I/Choreographer: Skipped 34 frames! The application may be doing too much work on its main thread.
W/Looper: PerfMonitor doFrame : time=11ms vsyncFrame=646410 latency=576ms procState=2 historyMsgCount=5 (msgIndex=1 wall=605ms seq=6 running=461ms late=300ms h=android.view.Choreographer$FrameHandler c=android.view.Choreographer$FrameDisplayEventReceiver)
I/ple.uploadimag: ProcessProfilingInfo new_methods=5563 is saved saved_to_disk=1 resolve_classes_delay=8000
I/Timeline: Timeline: Activity_launch_request time:17970735
W/ActivityThread: handleWindowVisibility: no activity for token android.os.BinderProxy@8f28626
W/ple.uploadimag: Accessing hidden method Landroid/view/View;->computeFitSystemWindows(Landroid/graphics/Rect;Landroid/graphics/Rect;)Z (greylist, reflection, allowed)
W/ple.uploadimag: Accessing hidden method Landroid/view/ViewGroup;->makeOptionalFitsSystemWindows()V (greylist, reflection, allowed)
I/Timeline: Timeline: Activity_launch_request time:17970975
W/Glide: Failed to find GeneratedAppGlideModule. You should include an annotationProcessor compile dependency on com.github.bumptech.glide:compiler in your application and a @GlideModule annotated AppGlideModule implementation or LibraryGlideModules will be silently ignored
I/System.out: 20:44:19.969 [main] INFO io.ktor.client.HttpClient - REQUEST: https://pancard-fraud.herokuapp.com/
20:44:19.978 [main] INFO io.ktor.client.HttpClient - METHOD: HttpMethod(value=POST)
I/System.out: 20:44:19.978 [main] INFO io.ktor.client.HttpClient - COMMON HEADERS
I/System.out: 20:44:19.981 [main] INFO io.ktor.client.HttpClient - -> Accept: application/json
20:44:19.982 [main] INFO io.ktor.client.HttpClient - -> Accept-Charset: UTF-8
I/System.out: 20:44:19.982 [main] INFO io.ktor.client.HttpClient - CONTENT HEADERS
20:44:19.983 [main] INFO io.ktor.client.HttpClient - -> Content-Length: 18
20:44:19.983 [main] INFO io.ktor.client.HttpClient - -> Content-Type: application/json
I/System.out: 20:44:19.984 [main] INFO io.ktor.client.HttpClient - BODY Content-Type: application/json
I/System.out: 20:44:20.024 [main] INFO io.ktor.client.HttpClient - BODY START
20:44:20.025 [main] INFO io.ktor.client.HttpClient - {"imageString":""}
20:44:20.025 [main] INFO io.ktor.client.HttpClient - BODY END
D/NetworkSecurityConfig: No Network Security Config specified, using platform default
I/DpmTcmClient: RegisterTcmMonitor from: $Proxy0
I/System.out: 20:44:41.745 [main] INFO io.ktor.client.HttpClient - RESPONSE: 503 Service Unavailable
20:44:41.746 [main] INFO io.ktor.client.HttpClient - METHOD: HttpMethod(value=POST)
I/System.out: 20:44:41.747 [main] INFO io.ktor.client.HttpClient - FROM: https://pancard-fraud.herokuapp.com/
I/System.out: 20:44:41.747 [main] INFO io.ktor.client.HttpClient - COMMON HEADERS
I/System.out: 20:44:41.748 [main] INFO io.ktor.client.HttpClient - -> cache-control: no-cache, no-store
I/System.out: 20:44:41.749 [main] INFO io.ktor.client.HttpClient - -> connection: keep-alive
I/System.out: 20:44:41.750 [main] INFO io.ktor.client.HttpClient - -> content-length: 506
I/System.out: 20:44:41.751 [main] INFO io.ktor.client.HttpClient - -> content-type: text/html; charset=utf-8
I/System.out: 20:44:41.752 [main] INFO io.ktor.client.HttpClient - -> date: Wed, 03 Nov 2021 15:14:40 GMT
I/System.out: 20:44:41.753 [main] INFO io.ktor.client.HttpClient - -> server: Cowboy
I/System.out: 20:44:41.754 [main] INFO io.ktor.client.HttpClient - -> x-android-received-millis: 1635952481690
I/System.out: 20:44:41.755 [main] INFO io.ktor.client.HttpClient - -> x-android-response-source: NETWORK 503
I/System.out: 20:44:41.756 [main] INFO io.ktor.client.HttpClient - -> x-android-selected-protocol: http/1.1
I/System.out: 20:44:41.757 [main] INFO io.ktor.client.HttpClient - -> x-android-sent-millis: 1635952461008
I/System.out: 20:44:41.769 [ktor-android-dispatcher-worker-3] INFO io.ktor.client.HttpClient - BODY Content-Type: text/html; charset=utf-8
I/System.out: 20:44:41.770 [ktor-android-dispatcher-worker-3] INFO io.ktor.client.HttpClient - BODY START
I/System.out: 20:44:41.771 [ktor-android-dispatcher-worker-3] INFO io.ktor.client.HttpClient - <!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta charset="utf-8">
<title>Application Error</title>
<style media="screen">
html,body,iframe {
margin: 0;
padding: 0;
}
html,body {
height: 100%;
overflow: hidden;
}
iframe {
width: 100%;
height: 100%;
border: 0;
}
</style>
</head>
<body>
<iframe src="//www.herokucdn.com/error-pages/application-error.html"></iframe>
</body>
</html>
I/System.out: 20:44:41.772 [ktor-android-dispatcher-worker-3] INFO io.ktor.client.HttpClient - BODY END
I/System.out: Error: Service Unavailable