This is going to be a long post because this spans the backend and the frontend of my application and I will detail every code from Laravel, Postman Json returns, and Kotlin Jetpack Compose. I am creating a questionnaire application where users can input their answers via radiobutton selection and then submit their answers. The UI looks like this:
Basically, users fill in each radiobutton, and it will be stored in the localhost phpMyAdmin database
One problem that I have been trying to debug for hours is that I can’t seem to submit the answers, and in the Android Studio Logcat, it says 500 Internal Server Error here in this screenshot:
Basically there is an answer and submit controller in the php laravel code that is related to an answers and results database to store the answers that are inputted. Here is the SubmitController:
<?php
namespace AppHttpControllers;
use IlluminateHttpRequest;
use AppModelsAnswer;
use AppModelsResult;
class SubmitController extends Controller
{
public function store(Request $request){
$request_answer = json_decode($request->input('answers'), true);
$totalScore = 0;
foreach ($request_answer as $key => $value) {
Answer::create([
'user_id' => $request->input('user_id'),
'question_id' => $value['question_id'],
'answer_value' => $value['answer'],
'created_at' => now(),
'updated_at' => now(),
]);
$totalScore += $value['answer'];
}
$result = Result::create([
'user_id' => $request->input('user_id'),
'fill_date'=> $request->input('fill_date'),
'score'=> $totalScore,
'created_at' => now(),
'updated_at' => now(),
]);
return response()->json(['message' => 'Answers submitted successfully', 'result' => ""]);
}
}
Next, is a screenshot of the submit api. I’ve already tested it and it works.
this here is the value inside the answers key for testng:
Next is the android code. There are a few code snippets that are related to this process, including data classes, the repository, viewmodel, and the view screens.
Here are the data classes:
data class AnswerModel(
val answers: List<Answer>,
val user_id: String,
val fill_date: String
)
next data class:
data class Answer(
val question_id: String,
val answer: String
)
final response data class:
data class AnswerResponse(
val message: String,
val result: Result
)
Next, this is the api code that is used for this POST api:
@Singleton
interface EMedibApi {
//api and post data
@POST("submit")
suspend fun submitAnswers(@HeaderMap headers: Map<String , String>,
@Body request: AnswerModel): AnswerResponse
}
After that, this is the view model (I use the submitAnswers function here):
@HiltViewModel
class DsmqViewModel @Inject constructor(private val repository: DsmqRepository) : ViewModel() {
var isLoading: Boolean by mutableStateOf(false)
var questions: QuestionResponse? by mutableStateOf(
QuestionResponse(
data = null
)
)
init {
fetchQuestions()
// fetchResults()
}
private fun fetchQuestions() {
viewModelScope.launch {
isLoading = true
try {
when (val response = repository.getQuestions()) {
is Resource.Success -> {
Log.d("DSMQ DATA", "${response.data}")
questions = response.data!!
}
is Resource.Error -> {
Log.d("DSMQ DATA", "${response.message}")
}
else -> {
Log.d("DSMQ DATA", "$response")
}
}
} catch (e: HttpException) {
Log.e("DsmqViewModel", "Failed to fetch questions: ${e.message}")
} finally {
isLoading = false
}
}
}
fun submitAnswers(answers: List<Answer>, headers: Map<String, String> , userId: String, fillDate: String) {
viewModelScope.launch {
isLoading = true
val answerModel = AnswerModel(
answers = answers,
user_id = userId,
fill_date = fillDate
)
Log.d("DSMQ SUBMIT", answerModel.toString())
try {
when (val response = repository.submitAnswers(answerModel, headers)) {
is Resource.Success -> {
Log.d("DSMQ SUBMIT", "Answers submitted successfully: ${response.data}")
}
is Resource.Error -> {
Log.d("DSMQ SUBMIT", "Failed to submit answers: ${response.message}")
}
else -> {
Log.d("DSMQ SUBMIT", "Unknown response: $response")
}
}
} catch (e: HttpException) {
Log.e("DsmqViewModel", "Failed to submit answers: ${e.response()}")
} finally {
isLoading = false
}
}
}
}
next, is the repository
//the dsmq repository
class DsmqRepository @Inject constructor(private val api: EMedibApi) {
suspend fun getQuestions(): Resource<QuestionResponse>{
Resource.Loading(data = true)
return try {
val dataQuestion = api.getQuestions()
Resource.Success(data = dataQuestion)
} catch (e: Exception) {
Resource.Error(message = e.message.toString())
} finally {
Resource.Loading(data = false)
}
}
//create function for submittingAnswers
suspend fun submitAnswers(answerModel: AnswerModel, headers: Map<String, String>): Resource<AnswerResponse> {
Resource.Loading(data = true)
return try {
val responseSubmit = api.submitAnswers(headers, answerModel)
Resource.Success(data = responseSubmit)
} catch (e: Exception) {
Resource.Error(message = e.message.toString())
} finally {
Resource.Loading(data = false)
}
}
}
Finally, this is the view/screens/functions that are related to submitting the answers.
QuestionItem.kt code:
@Composable
fun QuestionItem(
question: String,
options: List<Option>,
onAnswerSelected: (Answer) -> Unit
) {
var selectedOptionId by remember { mutableStateOf(-1) }
Column(modifier = Modifier.padding(16.dp)) {
Text(
text = question,
textAlign = TextAlign.Center,
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 8.dp)
)
val optionTexts = options.map { it.option_text ?: "No options provided" }
val textAboveOptions = listOf(
"Sangat berlaku bagi saya",
"Berlaku bagi saya",
"Sedikit berlaku bagi saya",
"Tidak berlaku bagi saya"
)
HorizontalRadioButton(
options = optionTexts,
textAboveOptions = textAboveOptions,
selectedOption = selectedOptionId,
onOptionSelected = { optionId ->
selectedOptionId = optionId
if (options.isNotEmpty()) {
val selectedOption = options.find { it.id == optionId }
selectedOption?.let {
val answer = Answer(
question_id = it.question_id.toString(),
answer = it.option_text
)
onAnswerSelected(answer)
}
}
}
)
}
}
Finally, the whole screen (the Answer data class is used after the startQuestionnaire if statement):
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun DsmqScreen(
navController: NavController,
viewModel: DsmqViewModel = hiltViewModel(),
) {
val customSelectedColor = Color(0xFF5799FC)
val lightGreen = Color(0xFF13ECA2)
val redColor = Color(0xFFF20D3F)
//val selectedAnswers = remember { mutableStateListOf<AnswerResponse>() }
val selectedAnswers = remember { mutableStateListOf<Answer>() }
var showDialog by remember { mutableStateOf(false) }
var showSubmitDialog by remember { mutableStateOf(false) }
var startQuestionnaire by remember { mutableStateOf(false) }
var selectedDate by remember { mutableStateOf("") }
var showInfoDialog by remember { mutableStateOf(false) }
Scaffold(
topBar = {
CenterAlignedTopAppBar(
title = {
Text(
text = "Diabetes Self-Management Questionnaire",
style = MaterialTheme.typography.bodyMedium,
textAlign = TextAlign.Center,
fontWeight = FontWeight.SemiBold,
color = Color.White
)
},
colors = TopAppBarDefaults.centerAlignedTopAppBarColors(containerColor = customSelectedColor)
)
}
) {
it
Column(
modifier = Modifier
.padding(16.dp)
.fillMaxHeight()
.verticalScroll(rememberScrollState())
) {
Spacer(modifier = Modifier.height(62.dp))
Button(
onClick = { showInfoDialog = true },
modifier = Modifier.align(Alignment.CenterHorizontally),
colors = ButtonDefaults.buttonColors(customSelectedColor)
) {
Text(text = "Apa itu Diabetes Self Management Questionnaire?", color = Color.White)
}
if (showInfoDialog) {
InfoAlertDialog(
onDismiss = { showInfoDialog = false }
)
}
EnterDate(selectedDate = selectedDate, onDateSelected = { selectedDate = it })
Spacer(modifier = Modifier.height(2.dp))
Button(
onClick = {
if (selectedDate.isNotEmpty()) {
showDialog = true
}
},
modifier = Modifier.align(Alignment.CenterHorizontally),
colors = ButtonDefaults.buttonColors(customSelectedColor)
) {
Text(text = "Mulai Isi Kuesioner")
}
Spacer(modifier = Modifier.height(20.dp))
StartQuestionnaireDialog(
showDialog = showDialog,
onDismiss = {
showDialog = false
},
onConfirm = {
showDialog = false
startQuestionnaire = true
},
confirmButtonColor = lightGreen,
dismissButtonColor = redColor
)
if (startQuestionnaire) {
Column {
viewModel.questions?.data?.forEach { it ->
QuestionItem(
question = it.question_text,
options = it.options ?: emptyList(),
onAnswerSelected = { answer ->
Log.d("DSMQ", "Answer Selected: $answer")
selectedAnswers.removeIf { it.question_id == answer.question_id }
selectedAnswers.add(answer)
}
)
}
//Spacer(modifier = Modifier.height(16.dp))
Button(
onClick = { showSubmitDialog = true },
modifier = Modifier.align(Alignment.CenterHorizontally),
colors = ButtonDefaults.buttonColors(customSelectedColor)
) {
Text(text = "Selesai Kuesioner", color = Color.White)
}
Spacer(modifier = Modifier.height(76.dp))
}
}
if (showSubmitDialog) {
AlertDialog(
onDismissRequest = { showSubmitDialog = false },
title = { Text(text = "Submit Answers") },
text = { Text(text = "Are you sure you want to submit your answers?") },
confirmButton = {
Button(
onClick = {
val headerMap = mutableMapOf<String, String>()
headerMap["Accept"] = "application/json"
showSubmitDialog = false
val userId = "1"
Log.d("selected Answer", selectedAnswers.toString())
viewModel.submitAnswers(
answers = selectedAnswers.toList(),
userId = userId,
fillDate = selectedDate,
headers = headerMap)
// Handle successful submission, e.g., navigate to result screen
// navController.popBackStack()
},
colors = ButtonDefaults.buttonColors(containerColor = lightGreen)
) {
Text(text = "Yes", color = Color.White)
}
},
dismissButton = {
Button(
onClick = { showSubmitDialog = false },
colors = ButtonDefaults.buttonColors(containerColor = redColor)
) {
Text(text = "No", color = Color.White)
}
}
)
}
//customprofile list tile to questionnaire
CustomTile(
title = "Hasil DSMQ",
subtitle ="Pantau Hasil DSMQ Anda" ,
leadingIcon = Icons.Outlined.Calculate,
onClick = {
//navigasi
}
)
Spacer(modifier = Modifier.height(96.dp))
}
}
}
I’ve been desperate to get answers and I have no idea why it keeps giving the 500 internal server error. I’ve already done:
php artisan serve
to activate the localhost server but it keeps giving me error code 500. I’ve already tested all the other APIs and they seem to work.