I am doing a post request, show the value in an alert dialog box and pass that information to another composable.
I don't know how to show the result in an alert dialog box and pass the information to another composable.
Here is what I have done so far.
#Composable
fun Lookup() {
val number = remember {
mutableStateOf(TextFieldValue())
}
val name = remember {
mutableStateOf(TextFieldValue())
}
val response = remember {
mutableStateOf("")
}
var showDialog by remember { mutableStateOf(false) }
if (showDialog) {
LookupDialog()
}
val openDialog by viewModel.open.observeAsState(false)
if (openDialog){
DialogBoxLoading()
}
Column(
modifier = Modifier.padding(20.dp),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Spacer(modifier = Modifier.height(20.dp))
TextField(
label = { Text(text = "Number") },
value = number.value,
onValueChange = { number.value = it })
Spacer(modifier = Modifier.height(20.dp))
Box(modifier = Modifier.padding(40.dp, 0.dp, 40.dp, 0.dp)) {
Button(
onClick = {
postDataUsingRetrofit("fjsjsdf")
LookupDialog
},
shape = RoundedCornerShape(50.dp),
modifier = Modifier
.fillMaxWidth()
.height(50.dp)
) {
Text(text = "Search Number")
}
}
}
}
fun postDataUsingRetrofit(number: String) {
val url = "https://baseurl/slookup"
val postBody = String.format(
"""{
"number": "%1${"$"}s",
"name": "%2${"$"}s"
}""", number, name
)
val requestBody = postBody.toRequestBody()
val okHttpClient = OkHttpClient()
val retrofit = Request.Builder()
.method("POST", requestBody)
.url(url)
.build()
okHttpClient.newCall(retrofit).enqueue(object : okhttp3.Callback {
override fun onFailure(call: okhttp3.Call, e: IOException) {
Log.d("itfailed", e.toString())
}
override fun onResponse(call: okhttp3.Call, response: okhttp3.Response) {
val json = JSONObject(response.body?.string())
val responseStatus = json.getString("status")
val responseDesc = json.getString("desc")
}
})
}
#Composable
fun LookupDialog(title: String = "",
isSuccessful: Boolean = false,
content: (#Composable () -> Unit)? = null,
clicked: () -> Unit = {}) {
AlertDialog(
onDismissRequest = { },
confirmButton = {
TextButton(onClick = {
//Move to a new composable(page)
})
{ Text(text = "OK") }
},
dismissButton = {
TextButton(onClick = {})
{ Text(text = "Cancel") }
},
title = { Text(text = "This should contain information responseStatus from postDataUsingRetrofit ") },
text = { Text(text = "This should contain information responseDesc from postDataUsingRetrofit ") }
)
}
#Composable
fun NewComposable(action: (Result?) -> Unit) {
Column(
modifier = Modifier.padding(20.dp),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Spacer(modifier = Modifier.height(20.dp))
TextField(
label = { Text(text = "This should contain information responseStatus from postDataUsingRetrofit ") },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
value = number.value,
onValueChange = { number.value = it })
Spacer(modifier = Modifier.height(20.dp))
TextField(
label = { Text(text = "This should contain information responseDesc from postDataUsingRetrofit ") },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
value = msisdn.value,
onValueChange = { name.value = it })
}
}
After postDataUsingRetrofit is done, the information shows in LookupDialog, that information passed to NewComposable.
I am trying to set an alarm using a TimePicker but the alarm ignores the time sent by parameter and instead it is executed immediately !!
This is my code:
class MainActivity : ComponentActivity() {
lateinit var navController: NavHostController
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
navController = rememberNavController()
NotePadReminderTheme {
SetupNavGraph(navController = navController)
}
}
}
}
#RequiresApi(Build.VERSION_CODES.M)
fun createAlarm(context: Context,time:Long){
val alarmManager =context.getSystemService(Context.ALARM_SERVICE) as? AlarmManager
val intent =Intent(context, AlarmReceiver::class.java)
Log.d("receivedtime","time $time")
val pendingIntent = PendingIntent.getBroadcast(context, Date().seconds, intent, PendingIntent.FLAG_IMMUTABLE)
alarmManager?.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, time, pendingIntent)
Toast.makeText(context,"Alarma creada",Toast.LENGTH_LONG).show()
}
#RequiresApi(Build.VERSION_CODES.M)
#Composable
fun AddPage(navController: NavController, vm: TaskViewModel = viewModel()) {
val scope= rememberCoroutineScope()
val title = remember {
mutableStateOf("")
}
val description = remember {
mutableStateOf("")
}
val date = remember {
mutableStateOf("")
}
val time = Calendar.getInstance()
val iscompleted = remember {
mutableStateOf(false)
}
val context = LocalContext.current
Column(
modifier = Modifier
.fillMaxSize()
.padding(top = 30.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = "Agregar Tarea",
style = NoteTheme.typography.h1
)
RowOfThis(title, "Título", true)
RowOfThis(description, "Descripción", false)
Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
DatePick(date)
}
Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
TimePick(time)
}
Button(
onClick = {
if (title.value.isNullOrBlank() || description.value.isNullOrBlank() ||
date.value.isNullOrBlank()
) {
Log.d("date","date:${date.value}")
Toast.makeText(context, "Por favor ingrese todos los datos", Toast.LENGTH_LONG)
.show()
return#Button
}
vm.task.value.title = title.value
vm.task.value.description = description.value
vm.task.value.date = date.value
vm.task.value.time = time.timeInMillis
Log.d("tmepickerreceived","date:${time.timeInMillis}")
scope.launch {
vm.createTask(context, vm.task.value)
createAlarm(context, time=time.timeInMillis). //here set the alarm ****************
Toast.makeText(context, "Tarea creada correctamente", Toast.LENGTH_LONG).show()
vm.resetTask()
}
},
colors = ButtonDefaults.buttonColors(backgroundColor = Color.Blue)
) {
Text(text = "Guardar", color = Color.White, style = NoteTheme.typography.subtitle)
}
}
}
#Composable
fun RowOfThis(value: MutableState<String>, label: String, single: Boolean) {
val focusManager = LocalFocusManager.current
Row(
modifier = Modifier
.fillMaxWidth()
.padding(10.dp)
) {
TextField(
modifier = Modifier
.fillMaxWidth()
.border(BorderStroke(width = 1.dp, color = Color.Black)),
singleLine = single,
maxLines = 3,
textStyle = NoteTheme.typography.body,
value = value.value, onValueChange = {
value.value = it
}, label = {
Text(
text = label, textAlign = TextAlign.Start,
color = Color.DarkGray,
style = NoteTheme.typography.subtitle
)
},
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
colors = TextFieldDefaults.textFieldColors(backgroundColor = Color.White),
keyboardActions = KeyboardActions(onNext =
{ focusManager.moveFocus((FocusDirection.Next)) })
)
}
}
#Composable
fun DatePick(mDate:MutableState<String>) {
val mContext = LocalContext.current
val mYear: Int
val mMonth: Int
val mDay: Int
val mCalendar = Calendar.getInstance()
mYear = mCalendar.get(Calendar.YEAR)
mMonth = mCalendar.get(Calendar.MONTH)
mDay = mCalendar.get(Calendar.DAY_OF_MONTH)
mCalendar.time = Date()
val mDatePickerDialog = DatePickerDialog(
mContext,
{ _: DatePicker, mYear: Int, mMonth: Int, mDayOfMonth: Int ->
mDate.value = "$mDayOfMonth/${mMonth + 1}/$mYear"
}, mYear, mMonth, mDay
)
mDatePickerDialog.datePicker.minDate=Date().time
Log.d("datepicker","dateset:${mDate.value}")
Button(modifier = Modifier
.padding(7.dp)
.fillMaxWidth(0.5f), onClick = {
mDatePickerDialog.show()
}, colors = ButtonDefaults.buttonColors(backgroundColor = Color(0XFF227093))) {
Text(text = "Seleccionar fecha",
color = Color.White, style = NoteTheme.typography.subtitle)
}
Text(text = "${mDate.value}",style = NoteTheme.typography.caption, fontSize = 22.sp, color = Color(0XFF227093))
}
#Composable
fun TimePick(mCalendar:Calendar) {
val mContext = LocalContext.current
val mHour = mCalendar[Calendar.HOUR_OF_DAY]
val mMinute = mCalendar[Calendar.MINUTE]
val mTime = remember { mutableStateOf("") }
val mTimePickerDialog = TimePickerDialog(
mContext,
{_, mHour : Int, mMinute: Int ->
mTime.value = "$mHour:$mMinute"
}, mHour, mMinute, false
)
Log.d("tmepicker","time:${mCalendar.timeInMillis}")
Button(modifier = Modifier
.padding(7.dp)
.fillMaxWidth(0.5f), onClick = {
mTimePickerDialog.show()
}, colors = ButtonDefaults.buttonColors(backgroundColor = Color(0XFF2c2c54))) {
Text(text = "Seleccionar hora",
color = Color.White, style = NoteTheme.typography.subtitle)
}
Text(text = "${mTime.value}",style = NoteTheme.typography.caption, fontSize = 22.sp, color = Color(0XFF2c2c54))
}
The problem I have is that I want the alarm to run at the scheduled time of the DatePicker ad TimePicker but it is running immediately !!
I don't know if this has something to do with the context, but how can I make the alarm be "scheduled" at the time and date of the DatePicker and Timepicker instead of firing at once
Well this was because when you get the calendar month you have to add one for some reason, so that was what caused an inaccurate time when setting the alarm. Final code:
class MainActivity : ComponentActivity() {
#RequiresApi(Build.VERSION_CODES.M)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
val context= LocalContext.current
AlarmTestTheme {
mainPage(context)
}
}
}
}
#RequiresApi(Build.VERSION_CODES.M)
#Composable
fun mainPage(context: Context) {
val scope= rememberCoroutineScope()
val calendar=Calendar.getInstance().apply {
timeInMillis=System.currentTimeMillis()
}
val title = remember {
mutableStateOf("")
}
val description = remember {
mutableStateOf("")
}
val year= remember {
mutableStateOf(calendar.get(Calendar.YEAR))
}
val month= remember {
mutableStateOf(calendar.get(Calendar.MONTH)+1)
}
val day= remember {
mutableStateOf(calendar.get(Calendar.DAY_OF_MONTH))
}
val hour= remember {
mutableStateOf(calendar.get(Calendar.HOUR_OF_DAY))
}
val minutes= remember {
mutableStateOf(calendar.get(Calendar.MINUTE))
}
val iscompleted = remember {
mutableStateOf(false)
}
Column(
modifier = Modifier
.fillMaxSize()
.padding(top = 30.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = "Agregar Tarea"
)
Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
DatePick(context = context, year = year, month = month, day = day)
}
Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
TimePick(context = context, hours = hour, minutes = minutes)
}
Button(
onClick = {
Log.d("tmepickerreceived","date and time :${year.value}/${month.value}/${day.value} : ${hour.value}:${minutes.value}")
calendar.apply {
set(Calendar.YEAR,year.value)
set(Calendar.MONTH,(month.value-1))
set(Calendar.DAY_OF_MONTH,day.value)
set(Calendar.HOUR_OF_DAY,hour.value)
set(Calendar.MINUTE,minutes.value)
}
val alarmMgr= context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
val shuff=(1..99999).shuffled().first()
val intent= Intent(context,AlarmReceiver::class.java)
intent.action="alarma"
intent.putExtra("ALARMA_STRING","alarm number: $shuff")
val pending=
PendingIntent.getBroadcast(context,shuff,intent, PendingIntent.FLAG_IMMUTABLE)
Log.e("time final","compare ${System.currentTimeMillis()}" +
" mytime: ${calendar.timeInMillis} " +
"hour: ${calendar.get(Calendar.HOUR_OF_DAY)} " +
"minute: ${calendar.get(Calendar.MINUTE)} " +
"year: ${calendar.get(Calendar.YEAR)} " +
"month: ${calendar.get(Calendar.MONTH)+1} " +
"day: ${calendar.get(Calendar.DAY_OF_MONTH)}")
alarmMgr?.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP,calendar.timeInMillis,pending)
Toast.makeText(context, "Alarm created", Toast.LENGTH_LONG)
.show()
},
colors = ButtonDefaults.buttonColors(backgroundColor = Color.Blue)
) {
Text(text = "Guardar", color = Color.White)
}
}
}
#Composable
fun DatePick(context: Context, year: MutableState<Int>,month: MutableState<Int>,day: MutableState<Int>) {
val mDatePickerDialog = DatePickerDialog(
context,{d,mYear,mMonth,mDay ->
year.value=mYear
month.value=mMonth+1
day.value=mDay
}, year.value, month.value, day.value
)
mDatePickerDialog.datePicker.minDate=System.currentTimeMillis() - 1000
Log.d("datepicker","${day.value}/${month.value}/${year.value}")
Button(modifier = Modifier
.padding(7.dp)
.fillMaxWidth(0.5f), onClick = {
mDatePickerDialog.show()
}, colors = ButtonDefaults.buttonColors(backgroundColor = Color(0XFF227093))) {
Text(text = "Seleccionar fecha",
color = Color.White)
}
Text(text = "${day.value}/${month.value}/${year.value}", fontSize = 22.sp, color = Color(0XFF227093))
}
#Composable
fun TimePick(context: Context,hours:MutableState<Int>,minutes:MutableState<Int>) {
val mTimePickerDialog = TimePickerDialog(
context,
{_, mHour : Int, mMinute: Int ->
hours.value=mHour
minutes.value=mMinute
}, hours.value, minutes.value, false
)
Log.d("tmepicker","hour selected :${hours.value}: munute selected: ${minutes.value}")
Button(modifier = Modifier
.padding(7.dp)
.fillMaxWidth(0.5f), onClick = {
mTimePickerDialog.show()
}, colors = ButtonDefaults.buttonColors(backgroundColor = Color(0XFF2c2c54))) {
Text(text = "Seleccionar hora",
color = Color.White)
}
Text(text = "${hours.value}:${minutes.value}", fontSize = 22.sp, color = Color(0XFF2c2c54))
}
My solution works, but sometimes when switching between boxes using openSheet() height of content cannot be calculated in time and it's jumps when opens.
Working solution using ModalBottomSheetLayout:
val sheetState = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden)
val scope = rememberCoroutineScope()
val density = LocalDensity.current
var topAppBarHeight = Dp.Unspecified
var sumHeight by remember { mutableStateOf(Dp.Unspecified) }
var currentSheet by remember { mutableStateOf(0) }
val openSheet: (sheet: Int) -> Unit = {
currentSheet = it
sumHeight = topAppBarHeight + (currentSheet * 50).dp
scope.launch { sheetState.show() }
}
val closeSheet: () -> Unit = {
scope.launch { sheetState.hide() }
sumHeight = Dp.Unspecified
}
ModalBottomSheetLayout(
modifier = Modifier.fillMaxSize(),
sheetState = sheetState,
sheetContent = {
Scaffold(
modifier = Modifier.height(sumHeight),
topBar = {
TopAppBar(
modifier = Modifier
.clickable(onClick = closeSheet)
.onGloballyPositioned { lc ->
topAppBarHeight = with(density) { lc.size.height.toDp() }
},
title = { Text("TopAppBarSheetContent") }
)
},
content = {
Box(
modifier = Modifier
.height((currentSheet * 50).dp)
.fillMaxWidth()
.background(Color.Blue)
)
},
)
},
content = {
Scaffold(
modifier = Modifier.fillMaxSize(),
topBar = {
TopAppBar(title = { Text("TopAppBarContent") })
},
content = { innerPadding ->
LazyColumn(
modifier = Modifier.padding(innerPadding),
contentPadding = PaddingValues(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
items(50) { item ->
Box(
modifier = Modifier
.clip(CircleShape)
.fillMaxWidth()
.height(75.dp)
.background(Color.Red)
.clickable { openSheet(item) }
)
}
}
}
)
}
)
Maybe there is a better way to solve this problem?
In the previous text field, when focused, there was a method in which all letters were selected. I found a way to make it remembered on the screen, but I wonder how to do it on the mvvm pattern.
#Composable
fun MainScreen(text: String, viewModel: HomeViewModel) {
val textState = remember { mutableStateOf(TextFieldValue()) }
val state = viewModel.mainState.text.collectAsState()
Column(
modifier = Modifier
.fillMaxWidth()
.fillMaxHeight(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = state.value,
color = Color.Blue,
fontSize = 40.sp
)
Button(
onClick = { viewModel.mainState.text.value = "New text" },
colors = ButtonDefaults.buttonColors(
backgroundColor = Color.Green
),
modifier = Modifier.padding(16.dp)
) {
Text(text)
}
TextField(
value = textState.value,
onValueChange = { textState.value = it },
label = { Text("Input text") }
)
}
}
The code above is from screen to remeber.
But I understand that remember is only declared within #Composable.
The view model does not declare #Composable, so I want to know how to do it in the mvvm pattern.
Below is my code.
LoginScreen
val text = viewModel.user_id.value
OutlinedTextField(
value = barcode,
onValueChange = {
viewModel.changeBarcode(it)
},
modifier = Modifier
.fillMaxWidth()
.padding(all = 4.dp)
.onFocusChanged { focusState ->
if (focusState.isFocused) {
//monitor value
}
},
label = { Text(text = "Barcode") },
singleLine = true,
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Number,
imeAction = ImeAction.Done
),
keyboardActions = KeyboardActions(
onDone = {
keyboardController?.hide()
viewModel.onTriggerEvent(MenuStateEvent.ScanEvent)
}
)
)
LoginViewModel
val user_id: MutableState<String> = mutableStateOf("")
How change it to mvvm pattern?
Maybe this will be the method you need.
in ViewModel:
val user_id = mutableStateOf(TextFieldValue(""))
in Compose:
TextField(
value = viewModel.user_id.value,
onValueChange = { viewModel.user_id.value = it },
label = { Text("Input text") }
)
Just change the mutableStateOf in ViewModel from "String" to "TextFieldValue(String)" to access it.
Dialog moves up when keyboard appears. I want a Composable do the same , but i cant find how to do this.
Ok, I've found the answer.
First, in manifest:
<activity
android:windowSoftInputMode="adjustResize">
After that use "Insets", that are explained in this page:
https://google.github.io/accompanist/insets/
in the following example I use a fab that makes a textfield appear that is positioned above the keyboard
PD: Notice in FloatingActionButton I use Modifier.systemBarsPadding(), it is made so that it is not hidden
class MainActivity : ComponentActivity() {
#ExperimentalAnimatedInsets
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
WindowCompat.setDecorFitsSystemWindows(window, false)
setContent {
MyApplicationTheme {
ProvideWindowInsets(windowInsetsAnimationsEnabled = true) {
val focusRequester = FocusRequester()
Greeting(focusRequester)
}
}
}
}
}
#ExperimentalAnimatedInsets
#Composable
fun Greeting(focusRequester: FocusRequester) {
var creatorVisibility by remember{ mutableStateOf(false)}
ProvideWindowInsets(windowInsetsAnimationsEnabled = true) {
Scaffold(floatingActionButton = {
Fab(onClick = { creatorVisibility = true }, creatorVisibility = creatorVisibility)}
) {
ConstraintLayout(constraintSet = constraints, modifier = Modifier.fillMaxSize(1f)) {
TextField(
value = TextFieldValue(),
onValueChange = { /*TODO*/ },
modifier = Modifier.layoutId("mainTf")
)
if (creatorVisibility){
Box(
modifier = Modifier
.fillMaxSize()
.clickable {
creatorVisibility = false
}
.background(color = Color(0f, 0f, 0f, 0.5f))
,contentAlignment = Alignment.BottomCenter
) {
SideEffect(effect = { focusRequester.requestFocus() })
TextField(colors = TextFieldDefaults.textFieldColors(backgroundColor = Color.White),
value = TextFieldValue(),
onValueChange = { /*TODO*/ },
modifier = Modifier
.layoutId("textField")
.imePadding()
.focusRequester(focusRequester = focusRequester)
)
}
}
}
}
}
}
#Composable
fun Fab(onClick : () -> Unit, creatorVisibility:Boolean){
if (!creatorVisibility){
FloatingActionButton(
onClick = { onClick() },
modifier = Modifier.layoutId("fab").systemBarsPadding()
) {
Text(text = "Crear tarea", modifier = Modifier.padding(start = 10.dp, end = 10.dp))
}
}
}
val constraints = ConstraintSet {
val textField = createRefFor("textField")
val mainTf = createRefFor("mainTf")
val fab = createRefFor("fab")
constrain(textField){
start.linkTo(parent.start)
end.linkTo(parent.end)
bottom.linkTo(parent.bottom)
}
constrain(mainTf){
start.linkTo(parent.start)
end.linkTo(parent.end)
top.linkTo(parent.top)
bottom.linkTo(parent.bottom)
}
constrain(fab){
end.linkTo(parent.end)
bottom.linkTo(parent.bottom)
}
}