I am creating an app that takes a List of Symptoms selected by users. The List is then sent to an ML model using API post. The ML model predicts the type of disease from the given list of Symptoms.
In given code i have stored the user selected symptoms list in selectedSymptoms. Now i tried to pass this list to the Api using Retrofit. In response to the list the Api will provide a response Which will be the type of disease in form of a String.
Main App
package com.example.searchdisease.ui
import android.util.Log
import android.widget.Toast
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.Checkbox
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.runtime.*
import androidx.compose.runtime.snapshots.SnapshotStateList
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.example.searchdisease.APIservice
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import retrofit2.Retrofit
import retrofit2.await
import retrofit2.converter.gson.GsonConverterFactory
//Here I have implemented the Api call and the UI for the symptom selection screen
class SymptomsData {
val symptomsList = listOf(
"itching", "skin_rash", "nodal_skin_eruptions", "continuous_sneezing", "shivering", "chills", "joint_pain", "stomach_pain", "acidity", "ulcers_on_tongue", "muscle_wasting", "vomiting", "burning_micturition", "spotting_ urination", "fatigue", "weight_gain", "anxiety", "cold_hands_and_feets", "mood_swings", "weight_loss", "restlessness", "lethargy", "patches_in_throat", "irregular_sugar_level", "cough", "high_fever", "sunken_eyes", "breathlessness", "sweating", "dehydration", "indigestion", "headache", "yellowish_skin", "dark_urine", "nausea", "loss_of_appetite", "pain_behind_the_eyes", "back_pain", "constipation", "abdominal_pain", "diarrhoea", "mild_fever", "yellow_urine", "yellowing_of_eyes", "acute_liver_failure", "fluid_overload", "swelling_of_stomach", "swelled_lymph_nodes", "malaise", "blurred_and_distorted_vision", "phlegm", "throat_irritation", "redness_of_eyes", "sinus_pressure", "runny_nose", "congestion", "chest_pain", "weakness_in_limbs", "fast_heart_rate", "pain_during_bowel_movements", "pain_in_anal_region", "bloody_stool", "irritation_in_anus", "neck_pain", "dizziness", "cramps", "bruising", "obesity", "swollen_legs", "swollen_blood_vessels", "puffy_face_and_eyes", "enlarged_thyroid", "brittle_nails", "swollen_extremeties", "excessive_hunger", "extra_marital_contacts", "drying_and_tingling_lips", "slurred_speech", "knee_pain", "hip_joint_pain", "muscle_weakness", "stiff_neck", "swelling_joints", "movement_stiffness", "spinning_movements", "loss_of_balance", "unsteadiness", "weakness_of_one_body_side", "loss_of_smell", "bladder_discomfort", "foul_smell_of urine", "continuous_feel_of_urine", "passage_of_gases", "internal_itching", "toxic_look_(typhos)", "depression", "irritability", "muscle_pain", "altered_sensorium", "red_spots_over_body", "belly_pain", "abnormal_menstruation", "dischromic _patches", "watering_from_eyes", "increased_appetite", "polyuria", "family_history", "mucoid_sputum", "rusty_sputum", "lack_of_concentration", "visual_disturbances", "receiving_blood_transfusion", "receiving_unsterile_injections", "coma", "stomach_bleeding", "distention_of_abdomen", "history_of_alcohol_consumption", "fluid_overload.1", "blood_in_sputum", "prominent_veins_on_calf", "palpitations", "painful_walking", "pus_filled_pimples", "blackheads", "scurring", "skin_peeling", "silver_like_dusting", "small_dents_in_nails", "inflammatory_nails", "blister", "red_sore_around_nose", "yellow_crust_ooze"
)
}
//This is the data class for the symptoms
data class SelectedSymptomsRequest(val symptoms: List<String>)
//This is the data class for the response from Modal
data class DiseasePredictionResponse(val disease: String)
@Composable
fun MainScreen() {
val symptomsData = SymptomsData()
val selectedSymptoms = remember { mutableStateListOf<String>() }
val coroutineScope = rememberCoroutineScope()
val prediction = remember { mutableStateOf("") }
val isButtonClicked = remember { mutableStateOf(false) }
Box{
SymptomSelectionScreen(symptomsData, selectedSymptoms) { symptom ->
selectedSymptoms.add(symptom)
}
Button(
onClick = {//Here I have implemented the Api call and the UI for the symptom selection screen
//Here the crash occurs
coroutineScope.launch {
val result = withContext(Dispatchers.IO) {
postDataUsingRetrofit(selectedSymptoms, prediction)
}
prediction.value = result.toString()
}
},
modifier = Modifier.align(Alignment.BottomCenter)
) {
Text("Predict")
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SymptomSelectionScreen(
symptomsData: SymptomsData,
selectedSymptoms: SnapshotStateList<String>,
onSymptomSelected: (String) -> Unit
) {
val searchText = remember { mutableStateOf("") }
val filteredSymptoms = remember(searchText.value) {
symptomsData.symptomsList.filter { it.contains(searchText.value, ignoreCase = true) }
}
Column(Modifier.padding(16.dp)) {
TextField(
value = searchText.value,
onValueChange = { searchText.value = it },
label = { Text("Search Symptoms") }
)
Spacer(modifier = Modifier.height(16.dp))
LazyColumn(
modifier = Modifier.weight(1f),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
items(filteredSymptoms) { symptom ->
SymptomItem(
symptom = symptom,
onSelected = { selectedSymptoms.add(it) }
)
}
}
Spacer(modifier = Modifier.height(16.dp))
Column {
Text("Selected Symptoms:")
for (symptom in selectedSymptoms) {
Text(symptom)
}
}
}
}
@Composable
fun SymptomItem(
symptom: String,
onSelected: (String) -> Unit
) {
Row(verticalAlignment = Alignment.CenterVertically) {
Checkbox(
checked = false,
onCheckedChange = null // TODO: Handle checkbox state
)
Spacer(modifier = Modifier.width(8.dp))
Text(text = symptom)
Spacer(modifier = Modifier.weight(1f))
IconButton(
onClick = { onSelected(symptom) }
) {
Icon(
imageVector = Icons.Default.Add,
contentDescription = "Add"
)
}
}
}
private suspend fun postDataUsingRetrofit(
selectedSymptoms: SnapshotStateList<String>,
result: MutableState<String>
) {
val url = "disease.swoyam.engineer"
// on below line we are creating a retrofit
// builder and passing our base url
val retrofit = Retrofit.Builder()
.baseUrl(url)
// as we are sending data in json format so
// we have to add Gson converter factory
.addConverterFactory(GsonConverterFactory.create())
// at last we are building our retrofit builder.
.build()
// below the line is to create an instance for our retrofit api class.
val retrofitAPI = retrofit.create(APIservice::class.java)
// passing data from our text fields to our model class.
val dataModel = SelectedSymptomsRequest(selectedSymptoms)
// calling a method to create an update and passing our model class.
val call: Call<DiseasePredictionResponse>? = retrofitAPI.sendSelectedSymptoms(dataModel)
// on below line we are executing our method.
try {
val response = call?.execute()
if (response?.isSuccessful == true) {
val predictionResponse: DiseasePredictionResponse? = response.body()
val disease = predictionResponse?.disease ?: "Unknown"
val resp = "Predicted Disease: $disease"
result.value = resp
} else {
result.value = "Error found: ${response?.message()}"
}
} catch (e: Exception) {
result.value = "Error found: ${e.message}"
}
}
@Preview
@Composable
fun PreviewMainScreen() {
MainScreen()
}
Every thing wors fine with the selection of symptoms except when i press the button to predict the App crashes.
This is my Api Interface
package com.example.searchdisease
import com.example.searchdisease.ui.DiseasePredictionResponse
import com.example.searchdisease.ui.SelectedSymptomsRequest
import retrofit2.Call
import retrofit2.http.Body
import retrofit2.http.POST
interface APIservice {
@POST("predicts")
suspend fun sendSelectedSymptoms(@Body dataModel: SelectedSymptomsRequest?): Call<DiseasePredictionResponse>?
}
project level gradle
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
id 'com.android.application' version '8.0.2' apply false
id 'com.android.library' version '8.0.2' apply false
id 'org.jetbrains.kotlin.android' version '1.7.20' apply false
}
app level gradle
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'kotlin-kapt'
}
android {
namespace 'com.example.searchdisease'
compileSdk 33
defaultConfig {
applicationId "com.example.searchdisease"
minSdk 24
targetSdk 33
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary true
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
buildFeatures {
compose true
}
composeOptions {
kotlinCompilerExtensionVersion '1.3.2'
}
packagingOptions {
resources {
excludes += '/META-INF/{AL2.0,LGPL2.1}'
}
}
}
dependencies {
implementation 'androidx.core:core-ktx:1.8.0'
implementation platform('org.jetbrains.kotlin:kotlin-bom:1.8.0')
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1'
implementation 'androidx.activity:activity-compose:1.5.1'
implementation platform('androidx.compose:compose-bom:2022.10.00')
implementation 'androidx.compose.ui:ui'
implementation 'androidx.compose.ui:ui-graphics'
implementation 'androidx.compose.ui:ui-tooling-preview'
implementation 'androidx.compose.material3:material3'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
androidTestImplementation platform('androidx.compose:compose-bom:2022.10.00')
androidTestImplementation 'androidx.compose.ui:ui-test-junit4'
debugImplementation 'androidx.compose.ui:ui-tooling'
debugImplementation 'androidx.compose.ui:ui-test-manifest'
implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.6.1"
implementation "com.squareup.retrofit2:retrofit:2.9.0"
implementation "com.squareup.retrofit2:converter-gson:2.5.0"
implementation "com.squareup.okhttp3:okhttp:4.9.1"
implementation("io.ktor:ktor-client-core:$ktor_version")
implementation("io.ktor:ktor-client-cio:$ktor_version")
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.3"
}
I am new to Api calling and Posting so help will be appreciated.