Posting Data to API. coroutineScope.launch() and Retrofit issue

94 Views Asked by At

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.

This is the Logcat Error

0

There are 0 best solutions below