I am working to save times which is chosen by users. Chosen times should be saved to firestore. In my logs, Log.d("FirestoreSave", "Saving Place: $placeInfo")
shows the times also creates availableTimes area in firestore but array is looking empty. Also other inputs from user shown without problem.
My Data Class
data class PlaceData(
val name: String = "",
val capacity: Int = 0,
val id: String = "",
val placeImageUrl: String = "",
val availableTimes: List<String> = emptyList() // Firestore can handle this
):Serializable
Repository Implementation
override suspend fun addPlace(placeData: PlaceData): Flow<RootResult<Boolean>> = flow {
emit(RootResult.Loading)
try {
val currentUser = firebaseAuth.currentUser
val userId = currentUser?.uid
if (userId != null) {
val placeId = firestore.collection("users").document(userId).collection("places")
.document().id
// Create a map of the data to save
val placeInfo = mapOf(
"name" to placeData.name,
"capacity" to placeData.capacity,
"id" to placeId,
"placeImageUrl" to placeData.placeImageUrl,
"availableTimes" to placeData.availableTimes // Ensure this is a List<String>
)
Log.d("FirestoreSave", "Saving place: $placeInfo")
// Save the data to Firestore
firestore.collection("users").document(userId).collection("places")
.document(placeId).set(placeInfo, SetOptions.merge())
.addOnSuccessListener {
Log.d("FirestoreSave", "Place saved successfully!")
}
.addOnFailureListener { e ->
Log.e("FirestoreSave", "Failed to save place: ", e)
}
emit(RootResult.Success(true))
} else {
emit(RootResult.Error("User ID is null"))
}
} catch (e: Exception) {
emit(RootResult.Error(e.message ?: "Something went wrong"))
}
}.flowOn(Dispatchers.IO)
My Ui
@Composable
fun AddPlaceDialog(
onDismiss: () -> Unit,
onSave: (PlaceData) -> Unit,
onImagePick: () -> Unit,
selectedImageUri: Uri?,
context: Context
) {
var expanded by remember { mutableStateOf(false) }
var customPlaceName by remember { mutableStateOf("") }
val availableTimes = remember { mutableStateListOf<String>() }
val timePickerDialog = remember { mutableStateOf(false) }
AlertDialog(
containerColor = Color.Black,
onDismissRequest = onDismiss,
title = {
Text(
text = "Add Place",
style = TextStyle(color = Color.White, fontSize = 25.sp)
)
},
text = {
Column(modifier = Modifier.fillMaxWidth().padding(16.dp)) {
// Image Picker
Box(
modifier = Modifier.fillMaxWidth()
.height(128.dp)
.clip(RoundedCornerShape(8.dp))
.background(Color.White)
.clickable { onImagePick() }
.align(Alignment.CenterHorizontally)
.border(1.dp, Color.Black, RoundedCornerShape(8.dp))
) {
selectedImageUri?.let { uri ->
Image(
painter = rememberAsyncImagePainter(uri),
contentDescription = "Selected Image",
modifier = Modifier.fillMaxSize(),
contentScale = ContentScale.Crop,
)
} ?: run {
Icon(
imageVector = Icons.Filled.Add,
contentDescription = "Select Image",
modifier = Modifier.align(Alignment.Center).size(50.dp),
tint = Color.Red
)
}
}
Spacer(modifier = Modifier.height(16.dp))
// Place Name Input
TextField(
colors = TextFieldDefaults.colors(
unfocusedContainerColor = Color.White,
focusedContainerColor = Color.White,
focusedTextColor = Color.Black,
focusedIndicatorColor = Color.Black,
unfocusedIndicatorColor = Color.Black,
unfocusedLabelColor = Color.Black,
focusedLabelColor = Color.Black,
cursorColor = Color.Black,
unfocusedTextColor = Color.Black
),
value = customPlaceName,
onValueChange = { customPlaceName = it },
label = { Text("Enter Place Name", fontSize = 13.sp, color = Color.Black) },
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(16.dp))
// Add Time Button
Button(
onClick = { timePickerDialog.value = true },
colors = ButtonDefaults.buttonColors(containerColor = Color.White),
modifier = Modifier.fillMaxWidth()
) {
Icon(imageVector = Icons.Filled.Add, contentDescription = "Add Time", tint = Color.Black)
Spacer(modifier = Modifier.width(8.dp))
Text("Add Available Time", color = Color.Black)
}
Spacer(modifier = Modifier.height(16.dp))
// Display Selected Times
availableTimes.forEachIndexed { index, time ->
Row(
modifier = Modifier.fillMaxWidth()
.padding(vertical = 4.dp)
.background(Color.Gray.copy(alpha = 0.2f), RoundedCornerShape(8.dp))
.padding(8.dp),
verticalAlignment = Alignment.CenterVertically
) {
Text(text = time, color = Color.White, modifier = Modifier.weight(1f))
IconButton(onClick = { availableTimes.removeAt(index) }) {
Icon(imageVector = Icons.Filled.Delete, contentDescription = "Remove", tint = Color.Red)
}
}
}
}
},
confirmButton = {
AuthButtonComponent(
value = "Save",
onClick = {
// if (selectedImageUri == null) {
// Toast.makeText(context, "Please choose an image.", Toast.LENGTH_SHORT).show()
// return@AuthButtonComponent
// }
// if (customPlaceName.isBlank()) {
// Toast.makeText(context, "Please enter a place name.", Toast.LENGTH_SHORT).show()
// return@AuthButtonComponent
// }
// if (availableTimes.isEmpty()) {
// Toast.makeText(context, "Please add at least one available time.", Toast.LENGTH_SHORT).show()
// return@AuthButtonComponent
// }
val placeData = PlaceData(
name = customPlaceName,
placeImageUrl = selectedImageUri.toString(),
availableTimes = availableTimes.toList()
)
Log.d("PlaceData", "Saving PlaceData: $placeData")
onSave(placeData)
onDismiss()
},
modifier = Modifier.width(70.dp),
fillMaxWidth = false,
heightIn = 40.dp,
firstColor = Color.Black,
secondColor = Color.Black
)
},
dismissButton = {
AuthButtonComponent(
value = "Cancel",
onClick = { onDismiss() },
modifier = Modifier.width(80.dp),
fillMaxWidth = false,
heightIn = 40.dp,
firstColor = Color.Black,
secondColor = Color.Black
)
}
)
// Time Picker Dialog
if (timePickerDialog.value) {
val timePicker = TimePickerDialog(
context,
{ _, hour, minute ->
val selectedTime = String.format("%02d:%02d", hour, minute)
val times = availableTimes.add(selectedTime)
val list = times.toString()
timePickerDialog.value = false
Log.d("AvailableTimes", availableTimes.toString())
},
24, 0, true
)
timePicker.show()
}
}
I dont understand why it is not working please help mee
I am working to save times which is chosen by users. Chosen times should be saved to firestore. In my logs, Log.d("FirestoreSave", "Saving Place: $placeInfo")
shows the times also creates availableTimes area in firestore but array is looking empty. Also other inputs from user shown without problem.
My Data Class
data class PlaceData(
val name: String = "",
val capacity: Int = 0,
val id: String = "",
val placeImageUrl: String = "",
val availableTimes: List<String> = emptyList() // Firestore can handle this
):Serializable
Repository Implementation
override suspend fun addPlace(placeData: PlaceData): Flow<RootResult<Boolean>> = flow {
emit(RootResult.Loading)
try {
val currentUser = firebaseAuth.currentUser
val userId = currentUser?.uid
if (userId != null) {
val placeId = firestore.collection("users").document(userId).collection("places")
.document().id
// Create a map of the data to save
val placeInfo = mapOf(
"name" to placeData.name,
"capacity" to placeData.capacity,
"id" to placeId,
"placeImageUrl" to placeData.placeImageUrl,
"availableTimes" to placeData.availableTimes // Ensure this is a List<String>
)
Log.d("FirestoreSave", "Saving place: $placeInfo")
// Save the data to Firestore
firestore.collection("users").document(userId).collection("places")
.document(placeId).set(placeInfo, SetOptions.merge())
.addOnSuccessListener {
Log.d("FirestoreSave", "Place saved successfully!")
}
.addOnFailureListener { e ->
Log.e("FirestoreSave", "Failed to save place: ", e)
}
emit(RootResult.Success(true))
} else {
emit(RootResult.Error("User ID is null"))
}
} catch (e: Exception) {
emit(RootResult.Error(e.message ?: "Something went wrong"))
}
}.flowOn(Dispatchers.IO)
My Ui
@Composable
fun AddPlaceDialog(
onDismiss: () -> Unit,
onSave: (PlaceData) -> Unit,
onImagePick: () -> Unit,
selectedImageUri: Uri?,
context: Context
) {
var expanded by remember { mutableStateOf(false) }
var customPlaceName by remember { mutableStateOf("") }
val availableTimes = remember { mutableStateListOf<String>() }
val timePickerDialog = remember { mutableStateOf(false) }
AlertDialog(
containerColor = Color.Black,
onDismissRequest = onDismiss,
title = {
Text(
text = "Add Place",
style = TextStyle(color = Color.White, fontSize = 25.sp)
)
},
text = {
Column(modifier = Modifier.fillMaxWidth().padding(16.dp)) {
// Image Picker
Box(
modifier = Modifier.fillMaxWidth()
.height(128.dp)
.clip(RoundedCornerShape(8.dp))
.background(Color.White)
.clickable { onImagePick() }
.align(Alignment.CenterHorizontally)
.border(1.dp, Color.Black, RoundedCornerShape(8.dp))
) {
selectedImageUri?.let { uri ->
Image(
painter = rememberAsyncImagePainter(uri),
contentDescription = "Selected Image",
modifier = Modifier.fillMaxSize(),
contentScale = ContentScale.Crop,
)
} ?: run {
Icon(
imageVector = Icons.Filled.Add,
contentDescription = "Select Image",
modifier = Modifier.align(Alignment.Center).size(50.dp),
tint = Color.Red
)
}
}
Spacer(modifier = Modifier.height(16.dp))
// Place Name Input
TextField(
colors = TextFieldDefaults.colors(
unfocusedContainerColor = Color.White,
focusedContainerColor = Color.White,
focusedTextColor = Color.Black,
focusedIndicatorColor = Color.Black,
unfocusedIndicatorColor = Color.Black,
unfocusedLabelColor = Color.Black,
focusedLabelColor = Color.Black,
cursorColor = Color.Black,
unfocusedTextColor = Color.Black
),
value = customPlaceName,
onValueChange = { customPlaceName = it },
label = { Text("Enter Place Name", fontSize = 13.sp, color = Color.Black) },
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(16.dp))
// Add Time Button
Button(
onClick = { timePickerDialog.value = true },
colors = ButtonDefaults.buttonColors(containerColor = Color.White),
modifier = Modifier.fillMaxWidth()
) {
Icon(imageVector = Icons.Filled.Add, contentDescription = "Add Time", tint = Color.Black)
Spacer(modifier = Modifier.width(8.dp))
Text("Add Available Time", color = Color.Black)
}
Spacer(modifier = Modifier.height(16.dp))
// Display Selected Times
availableTimes.forEachIndexed { index, time ->
Row(
modifier = Modifier.fillMaxWidth()
.padding(vertical = 4.dp)
.background(Color.Gray.copy(alpha = 0.2f), RoundedCornerShape(8.dp))
.padding(8.dp),
verticalAlignment = Alignment.CenterVertically
) {
Text(text = time, color = Color.White, modifier = Modifier.weight(1f))
IconButton(onClick = { availableTimes.removeAt(index) }) {
Icon(imageVector = Icons.Filled.Delete, contentDescription = "Remove", tint = Color.Red)
}
}
}
}
},
confirmButton = {
AuthButtonComponent(
value = "Save",
onClick = {
// if (selectedImageUri == null) {
// Toast.makeText(context, "Please choose an image.", Toast.LENGTH_SHORT).show()
// return@AuthButtonComponent
// }
// if (customPlaceName.isBlank()) {
// Toast.makeText(context, "Please enter a place name.", Toast.LENGTH_SHORT).show()
// return@AuthButtonComponent
// }
// if (availableTimes.isEmpty()) {
// Toast.makeText(context, "Please add at least one available time.", Toast.LENGTH_SHORT).show()
// return@AuthButtonComponent
// }
val placeData = PlaceData(
name = customPlaceName,
placeImageUrl = selectedImageUri.toString(),
availableTimes = availableTimes.toList()
)
Log.d("PlaceData", "Saving PlaceData: $placeData")
onSave(placeData)
onDismiss()
},
modifier = Modifier.width(70.dp),
fillMaxWidth = false,
heightIn = 40.dp,
firstColor = Color.Black,
secondColor = Color.Black
)
},
dismissButton = {
AuthButtonComponent(
value = "Cancel",
onClick = { onDismiss() },
modifier = Modifier.width(80.dp),
fillMaxWidth = false,
heightIn = 40.dp,
firstColor = Color.Black,
secondColor = Color.Black
)
}
)
// Time Picker Dialog
if (timePickerDialog.value) {
val timePicker = TimePickerDialog(
context,
{ _, hour, minute ->
val selectedTime = String.format("%02d:%02d", hour, minute)
val times = availableTimes.add(selectedTime)
val list = times.toString()
timePickerDialog.value = false
Log.d("AvailableTimes", availableTimes.toString())
},
24, 0, true
)
timePicker.show()
}
}
I dont understand why it is not working please help mee
Share Improve this question asked Feb 6 at 14:39 ponyboyltcponyboyltc 11 bronze badge New contributor ponyboyltc is a new contributor to this site. Take care in asking for clarification, commenting, and answering. Check out our Code of Conduct. 2
document#set()
finishes -- it is an asynchronous operation. You should move the emit calls inside theaddOnSuccessListener()
andaddOnFailureListener()
. – Jay Commented Feb 6 at 14:43