How to bring fun DatePicker's date value to fun TaskContent - kotlin

I just start to make and edit some android apps after taking some Udemy online courses. I'm beginner at programming myself, so please understand my questions will be basic level for most software engineers. I am editing the code using Jetpack compose. I'm making a todoapp. I used MVVM structure, it's got repository, viewmodel etc... It's first time to ask questions in stackoverflow.
Here is my question. If you need more info to help me, let me know in comment.
I added the date in the TaskContent Function. I can't bring the date data that I chose from DatePicker Function.
How can I modify the code to bring the date information I selected from fun datePicker to fun TaskContent?
#Composable
fun TaskContent(
title: String,
onTitleChange: (String) -> Unit,
description: String,
onDescriptionChange: (String) -> Unit,
priority: Priority,
onPrioritySelected: (Priority) -> Unit,
date: String,
onDateChange: (String) -> Unit
) {
Column(
modifier = Modifier
.fillMaxSize()
.background(MaterialTheme.colors.background)
.padding(all = LARGE_PADDING)
) {
OutlinedTextField(
modifier = Modifier.fillMaxWidth(),
value = title,
onValueChange = { onTitleChange(it) },
label = { Text(text = stringResource(id = R.string.title)) },
textStyle = MaterialTheme.typography.body1,
singleLine = true
)
Divider(
modifier = Modifier.height(MEDIUM_PADDING),
color = MaterialTheme.colors.background
)
PriorityDropDown(
priority = priority,
onPrioritySelected = onPrioritySelected
)
Divider(
modifier = Modifier.height(MEDIUM_PADDING),
color = MaterialTheme.colors.background
)
DatePicker(date = date, onDateChange = onDateChange)
OutlinedTextField(
modifier = Modifier.fillMaxSize(),
value = description,
onValueChange = { onDescriptionChange(it) },
label = { Text(text = stringResource(id = R.string.description)) },
textStyle = MaterialTheme.typography.body1
)
}
}
I want to choose the date from fun DatePicker and send the data all the way to viewmodel... other things like title, description, priority are connected all the way to viewmodel.
If I start the app, I can choose the date from calendar using fun DatePicker. However, I can't send the data to fun TaskContent..
#Composable
fun DatePicker(
date: String,
onDateChange: (String) -> Unit
){
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 mDate = remember { mutableStateOf("") }
val mDatePickerDialog = DatePickerDialog(
mContext,
{ _: DatePicker, mYear, mMonth, mDayOfMonth ->
mDate.value = "${mYear}year ${mMonth+1}month ${mDayOfMonth}day"
}, mYear, mMonth, mDay
)
Row(
modifier = Modifier
.fillMaxWidth()
.height(PRIORITY_DROP_DOWN_HEIGHT)
.clickable { mDatePickerDialog.show() }
.border(
width = 1.dp,
color = MaterialTheme.colors.onSurface.copy(alpha = ContentAlpha.disabled),
shape = MaterialTheme.shapes.small
).padding(12.dp),
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = "date: ${mDate.value}", style = MaterialTheme.typography.subtitle2)
}
}

Related

How do I make lazyVerticalGrid's scroll and column's verticalScroll, scroll together as one

I practicing jetpack compose, where I have a LazyVerticalGrid, a Text and a lazyColumn composeables, which I put in inside another composable which I called HomeContentScreen and gave it a scroll function, and as you know, the LasyVerticalGrid already has a built-in scroll function, which I tried to disable but it affected the scrolling function.
To Summarize; the three composeables scroll differently, depending on which part of the screen I touch.
So my question is how do I make them all follow the same scroll function (vertically)
Here's the codes.
|
Text Composeable;
#Composable
fun HomeQuote() {
Text(
text = stringResource(R.string.Lorem_ipsum),
style = MaterialTheme.typography.h2,
modifier = Modifier
.paddingFromBaseline(top = 24.dp, bottom = 18.dp)
.padding(horizontal = 18.dp)
.heightIn(min = 56.dp)
)
}
|
LazyVerticalGrid Composable;
#Composable
fun SecondBodyGrid(
modifier: Modifier = Modifier,
onHomeCardClick: (Int) -> Unit = {},
) {
LazyVerticalGrid(
columns = GridCells.Fixed(1),
contentPadding = PaddingValues(horizontal = 16.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
modifier = modifier
.height(1450.dp)
//.disabledVerticalPointerInputScroll()
.....
|
LazyRow Composable
#Composable
fun FirstBodyRow(
onHomeCardClick: (Int) -> Unit = {},
) {
LazyRow(
horizontalArrangement = Arrangement.spacedBy(8.dp),
contentPadding = PaddingValues(horizontal = 16.dp),
modifier = Modifier
) {
.....
|
And Finally the Composable I arranged everything, HomeContentScreen;
#Composable
fun HomeContentScreen(
modifier: Modifier = Modifier,
onHomeCardClick: () -> Unit
) {
Column(
modifier
.verticalScroll(rememberScrollState())
.padding(vertical = 16.dp)) {
HomeQuote()
HomeTitleSection(title = R.string.favorite_collections) {
SecondBodyGrid {onHomeCardClick()}
}
HomeTitleSection(title = R.string.align_your_body) {
FirstBodyRow {onHomeCardClick()}
}
}

How to implement Compose Navigation Arguments

I'm practicing Jetpack compose navigation, currently I'm stuck at passing arguments, so the correct information can be displayed when clicked.
I'm trying to navigate from this Destination, MenuScreen;
#Composable
fun HomeScreen(onHomeCardClick: () -> Unit) {
HomeContentScreen(onHomeCardClick = onHomeCardClick)
}
#Composable
fun HomeContentScreen(
modifier: Modifier = Modifier,
onHomeCardClick: () -> Unit
) {
Column(
modifier
.verticalScroll(rememberScrollState())
.padding(vertical = 16.dp)) {
HomeQuote()
....
}
}
To this destination, MenuInfoScreen;
#Composable
fun HomeInfoScreen(){
WelcomeText()
HomeInfoDetails()
}
#Composable
fun WelcomeText() {
Text(
text = "Welcome, to Home Information",
style = MaterialTheme.typography.h3,
modifier = Modifier.padding(horizontal = 12.dp, vertical = 18.dp)
)
}
#Composable
fun HomeInfoDetails(
homeInfo: HomeInfo
) {
Card(
modifier = Modifier
.padding(10.dp)
.clip(CircleShape),
elevation = 10.dp,
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(10.dp)
) {
Image(
painter = painterResource(id = homeInfo.homeInfoImageId),
contentDescription = null,
modifier = Modifier
.fillMaxWidth()
.clip(shape = RoundedCornerShape(topEnd = 4.dp, bottomEnd = 4.dp)),
contentScale = ContentScale.Fit
)
Spacer(modifier = Modifier.height(16.dp))
Text(
text = homeInfo.title,
style = MaterialTheme.typography.h3
)
Spacer(modifier = Modifier.height(16.dp))
Text(
text = homeInfo.description,
style = MaterialTheme.typography.h5
)
}
}
}
Here's the model I'm trying to follow, HomeInfoModel;
object HomeInfoModel {
val homeInfoModelList = listOf(
HomeInfo(
id = 1,
title = "Monty",
sex = "Male",
age = 14,
description = "Monty enjoys chicken treats and cuddling while watching Seinfeld.",
homeInfoImageId = R.drawable.ab1_inversions
),
.....
)
}
Here's the my NavHost;
NavHost(
navController = navController,
startDestination = Home.route,
modifier = modifier
) {
// builder parameter will be defined here as the graph
composable(route = Home.route) {
HomeScreen(
onHomeCardClick = {}
)
}
....
composable(route = HomeInfoDestination.route) {
HomeInfoScreen()
}
}
}
And my Destinations file;
object Home : KetoDestination {
override val route = "home"
}
....
object HomeInfoDestination : KetoDestination{
override val route = "homeInfo"
}
I know this is a lot and I'm a bit off, but please I've been stuck here for more than a week now. Any little information willl surely come handy.
And Of Course very much appreciated.
Thanks for your help in advance.
You should extract the arguments from the NavBackStackEntry that is available in the lambda of the composable() function.
You can read more in the official android docs https://developer.android.com/jetpack/compose/navigation#nav-with-args

How to send and get argument in composable in jetpack compose?

I'm new in jetpack compose. I developed an app by XML and Kotlin and want to move it to jetpack compose. The problem is that I have one activity and one fragment that there are several CardViews in the activity and when clicking on them they send a specific key to the fragment and the fragment shows specific information related to that CardView (I just have used one fragment), now in jetpack compose I don't know how to implement it. Could anyone help me, please? I have no problem with navigation I want to know how to change an Image and some Texts. I wonder that how can I use
when (key) {"key" -> text = ... imageResource = ...} in compose or not?
this is my code for XML
class BaseFragment : Fragment() {
internal lateinit var view: View
private lateinit var imgMain : ImageView
private lateinit var txtIngredients : TextView
private lateinit var txtMaking : TextView
private lateinit var txtBeCareful : TextView
#Suppress("UNREACHABLE_CODE")
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
view = inflater.inflate(R.layout.base_fragment, container,
false)
setupViews()
return view
}
private fun setupViews() {
imgMain = view.findViewById(R.id.img_BaseFragment_mainImg)
txtIngredients =
view.findViewById(R.id.txt_BaseFragment_ingredients)
txtMaking = view.findViewById(R.id.txt_BaseFragment_making)
txtBeCareful =
view.findViewById(R.id.txt_BaseFragment_beCareFullText)
this.arguments?.let {
when (it.getString(key: "itemTitle", defaultVlue :"")) {
"mas1" -> {
makeContent(R.drawable.mas1,
R.string.mas1,
R.string.mas1_making,
R.string.mas1_text)
}
"mas2" -> {
makeContent(R.drawable.mas2,
R.string.mas2,
R.string.mas2_making,
R.string.mas2_text)
}
"mas3" -> {
makeContent(R.drawable.mas3,
R.string.mas3,
R.string.mas3_making,
R.string.mas3_text)
}
}
private fun makeContent(imgCont : Int, txtIngredientsCont : Int,
txtMakingCont : Int, txtBeCarCont : Int ){
imgMain.setImageResource(imgCont)
txtIngredients.setText(txtIngredientsCont)
txtMaking.setText(txtMakingCont)
txtBeCareful.setText(txtBeCarCont)
}
}
and this is my code in jetpack compose but it doesn't work
#SuppressLint("UnusedMaterialScaffoldPaddingParameter")
#Composable
fun FinalShowScreen(itemTitle: String? = null, navController:
NavController) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(3.dp)
.verticalScroll(rememberScrollState())
) {
var imageId: Int
var textIngredientsId: Int
var textMakingId: Int
var textBeCarefulId: Int
when (maskArg) {
"mas1" -> {
imageId = R.drawable.mas1
textIngredientsId = R.string.mas1
textMakingId = R.string.mas1making
textBeCarefulId = R.string.mas1_text
}
"mas2" -> {
imageId = R.drawable.mas2
textIngredientsId = R.string.mas2
textMakingId = R.string.mas2making
textBeCarefulId = R.string.mas2_text
}
"mas3" -> {
imageId = R.drawable.mas3
textIngredientsId = R.string.mas3
textMakingId = R.string.mas3making
textBeCarefulId = R.string.mas3_text
}
Image(
painter = painterResource(id =
imageId),
contentDescription = "",
modifier = Modifier
.padding(bottom = 8.dp)
.height(200.dp)
.fillMaxSize()
)
Text(
text = stringResource(id = textIngredientsId),
style = typography.h1,
color = MaterialTheme.colors.primary,
textAlign = TextAlign.Center,
modifier = Modifier
.padding(8.dp)
)
Text(
text = stringResource(id =
textMakingId),
style = typography.h2,
color = MaterialTheme.colors.primaryVariant,
textAlign = TextAlign.Start,
modifier = Modifier
.padding(start = 8.dp, end = 8.dp, bottom = 15.dp,
top = 8.dp)
)
Text(
text = stringResource(id =
textBeCarefulId),
style = typography.h2,
color = MaterialTheme.colors.primaryVariant,
textAlign = TextAlign.Start,
modifier = Modifier
.padding(8.dp)
)
}
}
Finally, my problem was solved when, I changed the variables to
#DrawableRes imageId: Int,
#StringRes textIngredientsId: Int,
#StringRes textMakingId: Int,
#StringRes textBeCarefulId: Int
and I changed the navigation method in the Navgragh to this
class MainActions(navController: NavController) {
val gotoFinalShow: (String) -> Unit = { maskArg ->
navController.navigate("${Screens.FinalShow.route}/$maskArg"){
launchSingleTop = true
restoreState = true
}
}
}

Create vertical slider with label in Jetpack Compose

I made a vertical slider based on this answer, and now I need to add title and value for each sliders.
If I set a fixed width value in modifier like this modifier.width(180.dp), it looks fine like this
However I would like to let the slider height be responsive to the device screen size, so I set the width to modifier.fillMaxWidth(), the bottom text will disappear
Here is my vertical slider compose looks like, and I try to set the height in modifier here.
#Composable
fun VerticalSlider(value: MutableState<Float>, min: Int, max: Int, onFinished:(Int)->Unit) {
Slider(
modifier = Modifier
.graphicsLayer {
rotationZ = 270f
transformOrigin = TransformOrigin(0f, 0f)
}
.layout { measurable, constraints ->
val placeable = measurable.measure(
Constraints(
minWidth = constraints.minHeight,
maxWidth = constraints.maxHeight,
minHeight = constraints.minWidth,
maxHeight = constraints.maxWidth,
)
)
layout(placeable.height, placeable.width) {
placeable.place(-placeable.width, 0)
}
}
// .fillMaxWidth()
.width(180.dp)
.height(50.dp)
,
value = value.value,
valueRange = min.toFloat()..max.toFloat(),
onValueChange = {
value.value = it.toInt().toFloat()
},
onValueChangeFinished = {
onFinished(value.value.toInt())
},
)
}
Vertical slider with text.
#Composable
fun VerticalSliderWithText(sliderValue: MutableState<Float>, min: Int, max: Int, onFinished:(Int)->Unit) {
var sliderValue = remember { mutableStateOf(0f) }
Column(
modifier = Modifier
.fillMaxSize(),
verticalArrangement = Arrangement.SpaceAround,
horizontalAlignment = Alignment.CenterHorizontally
){
Text("slider title")
VerticalSlider(value = sliderValue, min = -6, max = 6,
) {
//println(" finish value: $it")
}
Text(modifier = Modifier.background(Green),
text="slider value")
}
}
Instead of Modifier.fillMaxWidth, you need to use Modifier.weight, which is available inside a Column. To do so you need to add a modifier parameter:
#Composable
fun VerticalSlider(value: MutableState<Float>, min: Int, max: Int, onFinished:(Int)->Unit, modifier: Modifier) {
Slider(
modifier = modifier
//...
So you can pass Modifier.weight like this:
Column(
modifier = Modifier
.fillMaxSize(),
verticalArrangement = Arrangement.SpaceAround,
horizontalAlignment = Alignment.CenterHorizontally
){
Text("slider title")
VerticalSlider(
value = sliderValue,
min = -6,
max = 6,
onFinished = {
//println(" finish value: $it")
},
modifier = Modifier.weight(1f)
)
Alternatively, you can declare VerticalSlider on ColumnScope, so Modifier.weight can be used directly from the function:
#Composable
fun ColumnScope.VerticalSlider(value: MutableState<Float>, min: Int, max: Int, onFinished:(Int)->Unit) {
Slider(
modifier = Modifier
.weight(1f)
//...

Compose navigation title is not updating

I am trying to update the title of the TopAppBar based on a live data in the ViewModel, which I update on different screens. It looks like the live data is getting updated properly, but the update is not getting reflected on the title of the TopAppBar. Here is the code:
class MainActivity : ComponentActivity() {
#ExperimentalFoundationApi
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
LazyVerticalGridActivityScreen()
}
}
}
#ExperimentalFoundationApi
#Composable
fun LazyVerticalGridActivityScreen(destinationViewModel: DestinationViewModel = viewModel()) {
val navController = rememberNavController()
var canPop by remember { mutableStateOf(false) }
// getting the latest title value from the view model
val title: String by destinationViewModel.title.observeAsState("")
Log.d("MainActivity_title", title) // not getting called
navController.addOnDestinationChangedListener { controller, _, _ ->
canPop = controller.previousBackStackEntry != null
}
val navigationIcon: (#Composable () -> Unit)? =
if (canPop) {
{
IconButton(onClick = { navController.popBackStack() }) {
Icon(imageVector = Icons.Filled.ArrowBack, contentDescription = null)
}
}
} else {
null
}
Scaffold(
topBar = {
TopAppBar(title = { Text(title) }, navigationIcon = navigationIcon) // updating the title
},
content = {
NavHost(navController = navController, startDestination = "home") {
composable("home") { HomeScreen(navController) }
composable("details/{listId}") { backStackEntry ->
backStackEntry.arguments?.getString("listId")?.let { DetailsScreen(it, navController) }
}
}
}
)
}
#ExperimentalFoundationApi
#Composable
fun HomeScreen(navController: NavHostController, destinationViewModel: DestinationViewModel = viewModel()) {
val destinations = DestinationDataSource().loadData()
// updating the title in the view model
destinationViewModel.setTitle("Lazy Grid Layout")
LazyVerticalGrid(
cells = GridCells.Adaptive(minSize = 140.dp),
contentPadding = PaddingValues(8.dp)
) {
itemsIndexed(destinations) { index, destination ->
Row(Modifier.padding(8.dp)) {
ItemLayout(destination, index, navController)
}
}
}
}
#Composable
fun ItemLayout(
destination: Destination,
index: Int,
navController: NavHostController
) {
Column(
verticalArrangement = Arrangement.Center,
modifier = Modifier
.background(MaterialTheme.colors.primaryVariant)
.fillMaxWidth()
.clickable {
navController.navigate("details/$index")
}
) {
Image(
painter = painterResource(destination.photoId),
contentDescription = stringResource(destination.nameId),
modifier = Modifier.fillMaxWidth(),
contentScale = ContentScale.Crop
)
Text(
text = stringResource(destination.nameId),
color = Color.White,
fontSize = 14.sp,
modifier = Modifier.padding(horizontal = 16.dp, vertical = 10.dp)
)
}
}
#Composable
fun DetailsScreen(
index: String,
navController: NavController,
destinationViewModel: DestinationViewModel = viewModel()
) {
val dataSource = DestinationDataSource().loadData()
val destination = dataSource[index.toInt()]
val destinationName = stringResource(destination.nameId)
val destinationDesc = stringResource(destination.descriptionId)
val destinationImage = painterResource(destination.photoId)
// updating the title in the view model
destinationViewModel.setTitle("Destination Details")
Column(
modifier = Modifier
.padding(16.dp)
.fillMaxWidth()
.verticalScroll(rememberScrollState())
) {
Image(
painter = destinationImage,
contentDescription = destinationName,
contentScale = ContentScale.Crop,
modifier = Modifier.fillMaxWidth()
)
Column(modifier = Modifier.padding(horizontal = 16.dp)) {
Text(
text = destinationName,
fontSize = 24.sp,
fontWeight = FontWeight.Bold,
modifier = Modifier.padding(vertical = 16.dp)
)
Text(text = destinationDesc, lineHeight = 24.sp)
Row(horizontalArrangement = Arrangement.End, modifier = Modifier.fillMaxWidth()) {
OutlinedButton(
onClick = {
navController.navigate("home") {
popUpTo("home") { inclusive = true }
}
},
modifier = Modifier.padding(top = 24.dp)
) {
Image(
imageVector = Icons.Filled.ArrowBack,
contentDescription = null,
colorFilter = ColorFilter.tint(MaterialTheme.colors.primaryVariant),
modifier = Modifier.size(20.dp)
)
Text("Back to Destinations", modifier = Modifier.padding(start = 16.dp))
}
}
}
}
}
EDIT: The ViewModel
class DestinationViewModel : ViewModel() {
private var _title = MutableLiveData("")
val title: LiveData<String>
get() = _title
fun setTitle(newTitle: String) {
_title.value = newTitle
Log.d("ViewModel_title", _title.value.toString())
Log.d("ViewModelTitle", title.value.toString())
}
}
Can anyone please help to find the bug? Thanks!
Edit:
Here is the GitHub link of the project: https://github.com/rawhasan/compose-exercise-lazy-vertical-grid
The reason it's not working is because those are different objects created in different scopes.
When you're using a navController, each destination will have it's own scope for viewModel() creation. By the design you may have a view model for each destination, like HomeViewModel, DestinationViewModel, etc
You can't access an other destination view model from current destination scope, as well as you can't access view model from the outer scope(which you're trying to do)
What you can do, is instead of trying to retrieve it with viewModel(), you can pass outer scope view model to your composable:
composable("details/{listId}") { backStackEntry ->
backStackEntry.arguments?.getString("listId")?.let { DetailsScreen(it, navController, destinationViewModel) }
}
Check out more details about viewModel() in the documentation
Another problem with your code is that you're calling destinationViewModel.setTitle("Lazy Grid Layout") inside composable function. So this code will be called many times, which may lead to recomposition.
To call any actions inside composable, you need to use side-effects. LaunchedEffect in this case:
LaunchedEffect(Unit) {
destinationViewModel.setTitle("Destination Details")
}
This will be called only once after view appear. If you need to call it more frequently, you need to specify key instead of Unit, so it'll be recalled each time when the key changes
You might wanna have a look here https://stackoverflow.com/a/68671477/15880865
Also, you do not need to paste all the code in the question. Just the necessary bit. We shall ask for it if something is missing in the question. Just change your LiveData to mutableStateOf and then let me know if that fixed your problem. Also, you do not need to call observeAsState after modifying the type. Just refer to the link it contains all the info.
Thanks