In my project i want to display the paths of gallery photos using lazy column in jetpack compose but when i select any photos and return back to screen , lazy column dost not show any paths. This my code
#Composable
fun ItemScreen(){
Column(modifier = Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState())
.padding(10.dp)) {
Headline(modifier = Modifier.fillMaxWidth())
AddItemRow(titleName = "Enter Color", KeyboardOptions(keyboardType = KeyboardType.Text))
OpenGallery()
}
#Composable
fun OpenGallery(){
var selectedImage = remember { mutableStateListOf<Uri?>() }
val launcher = rememberLauncherForActivityResult(contract = ActivityResultContracts.GetMultipleContents()){
selectedImage = it.toMutableStateList()
}
GalleryContent(selectedImage) {
launcher.launch("image/jpeg")
}
}
#Composable
fun GalleryContent(
selectedImage: MutableList<Uri?> ?= null,
OnImageClick: () -> Unit
)
{
if (selectedImage?.isEmpty() == true) {
Button(
onClick = OnImageClick,
) {
Text(text = "Choose Photos", color = Color.White, style = MaterialTheme.typography.bodySmall)
}
}
else{
LazyColumn(modifier = Modifier.height(300.dp)) {
items(selectedImage!!){ it ->
Text(text = it?.path.toString(), color = Color.Black)
Spacer(modifier = Modifier.height(9.dp))
}
}
}
}
How do i fix it . Please help
Instead of selectedImage = it.toMutableStateList() you can use:
val selectedImage = remember { mutableStateListOf<Uri?>(null) }
val launcher = rememberLauncherForActivityResult(ActivityResultContracts.GetMultipleContents()) {
selectedImage.apply {
clear()
addAll(it)
}
}
Related
fun Conversation(mainViewModel: MainViewModel) {
var stat = mainViewModel.getTweet().observeAsState()
val listState = rememberScrollState()
stat?.let {
if( stat.value == null){
Log.d("DAD","DASSA")
// null 값이면 로딩 에니메이션 뜨게 만들면 좋을듯>????
}
else{
Log.d("DAD","됐다")
var author = it.value!!.first
var messages = it.value!!.second
LazyColumn(modifier = Modifier.verticalScroll(listState).height(100.dp)) {
items(messages) { message ->
MessageCard(author, message)
}
}
}
}
}
In this composable, it seems like the composable "DID NOT" update value of mutableLivedata( mainViewModel.getTweet()). but I confuse about below code
fun InfoCard(mainViewModel: MainViewModel){
val Name = mainViewModel.getData().observeAsState()
Name.value?.let { it ->
Row(modifier = Modifier
.fillMaxWidth()
.background(shape = RoundedCornerShape(6.dp), color = Color.LightGray)
.padding(30.dp)
.height(40.dp),
verticalAlignment = Alignment.CenterVertically
) {
Spacer(modifier = Modifier.height(15.dp))
Text(it.get(0), modifier = Modifier.width(200.dp), textAlign = TextAlign.Center)
Spacer(modifier = Modifier.width(30.dp))
Text(text = it.get(1))
}
}
}
it also composable and use value of mutableLivedata( mainViewModel.getData())
But IT UPDATE VERY WELL..
what is a difference and How can I update first composable..
Self solution for someone like me.
Change
val Name = mainViewModel.getData().observeAsState()
to
val Name by mainViewModel.getData().observeAsState()
I am new in Compose Navigation. I have Button and when I clicked, I called the function in Viewmodel and trigger loading event with using StateFlow. So I called the next screen through navigation and calling loading spinner. I used delay(5000) to show spinner more before getting data but spinner is loading after the data is loaded. Can someone guide me.
MainActivityViewModel.kt
class MainActivityViewModel(private val resultRepository: ResultRepository) : ViewModel() {
val stateResultFetchState = MutableStateFlow<ResultFetchState>(ResultFetchState.OnEmpty)
fun getSportResult() {
viewModelScope.launch {
stateResultFetchState.value = ResultFetchState.IsLoading
val result = resultRepository.getSportResult()
delay(5000)
result.handleResult(
onSuccess = { response ->
if (response != null) {
stateResultFetchState.value = ResultFetchState.OnSuccess(response)
} else {
stateResultFetchState.value = ResultFetchState.OnEmpty
}
},
onError = {
stateResultFetchState.value =
ResultFetchState.OnError(it.errorResponse?.errorMessage)
}
)
}
}
}
SetupMainActivityView.kt
#OptIn(ExperimentalMaterial3Api::class)
#Composable
fun SetupMainActivityView(
viewModel: MainActivityViewModel = koinViewModel(),
navigateToNext: () -> Unit,
) {
Scaffold(topBar = {
TopAppBar(
title = { Text(text = stringResource(id = R.string.app_name)) },
backgroundColor = getBackgroundColor(),
elevation = 0.dp
)
}, content = { padding ->
Column(
modifier = Modifier
.fillMaxSize()
.background(getBackgroundColor())
.padding(padding),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Button(onClick = {
viewModel.getSportResult()
}) {
Text(text = stringResource(id = R.string.get_result))
}
}
})
when (val state = viewModel.stateResultFetchState.collectAsState().value) {
is ResultFetchState.OnSuccess -> {}
is ResultFetchState.IsLoading -> {
navigateToNext()
}
is ResultFetchState.OnError -> {}
is ResultFetchState.OnEmpty -> {}
}
}
My whole project link. Can someone guide me how can I show loading spinner after loading the next screen. Thanks
UPDATE
NavigationGraph.kt
#Composable
internal fun NavigationGraph() {
val navController = rememberNavController()
NavHost(navController = navController, startDestination = ScreenRoute.Home.route) {
composable(ScreenRoute.Home.route) {
SetupMainActivityView{
navController.navigate(ScreenRoute.Result.route)
}
}
composable(ScreenRoute.Result.route) {
ResultScreen()
}
}
}
ResultScreen.kt
#Composable
fun ResultScreen() {
CircularProgressIndicator()
}
please check my repository if you need more code. I added my github link above. Thanks
I can't see your code handling the Spinner. Anyway, a general idea to handle these kinda situations is
val state = remember{mutableStateOf<ResultFetchState>(ResultFetchState.EMPTY)}
if(state == ResultFetchState.LOADING){
//show spinner
Spinner()
}
...
state.value = viewModel.stateResultFetchState.collectAsState().value
A quick preface, I'm probably overlooking something rather simple but I'm willing to admit I'm not 100% sure of what I'm doing! The project I'm working on is a just for fun project to tinker around with Kotlin and Jetpack Compose by making an Android application.
So to my problem, I'm trying to use a DatePicker dialog using the vanpra compose-material-dialogs. I can get the dialog datepicker to appear but I can't get my selected date to return to my textfield I made after selecting the date from the dialog. It seems I'm running in to some issues updating the viewmodel correctly. Any help at all is greatly appreciated! Thank you!
Below is my DatePicker composable:
#Composable
fun TradeDatePicker(
value: LocalDate,
onValueChanged: (LocalDate) -> Unit,
modifier:Modifier = Modifier,
) {
val dialogState = rememberMaterialDialogState()
MaterialDialog(
dialogState = dialogState,
buttons = {
positiveButton("OK")
negativeButton("CANCEL")
},
backgroundColor = MaterialTheme.colors.background
) {
this.datepicker(onDateChange = onValueChanged)
}
Box(
modifier = modifier
.border(
width = 1.dp,
color = MaterialTheme.colors.onPrimary,
shape = RoundedCornerShape(50),
)
.clip(ButtonShape) //clip keeps animation within box borders
.clickable {
dialogState.show()
}
){
Row (
modifier = Modifier
.padding(16.dp),
){
Text(
text = "Trade Date",
modifier = Modifier
.weight(1F),
)
Icon(
Icons.Default.DateRange,
contentDescription = "Select Date",
)
}
}
}
Here is my screen where you select the datepicker field to display the datepicker dialog, it seems I'm trying to pass a LocalDate to the viewmodel but it requires a long(that's how I thought the date got returned from the dialog, as a long):
#Composable
fun AddEditTradeScreen(
navController: NavController,
viewModel: AddEditTradeViewModel = hiltViewModel()
){
val tradesymbolState = viewModel.tradeSymbol.value
val tradequantState = viewModel.tradeQuantity.value
val tradeDateState = viewModel.tradeDate
val scaffoldState = rememberScaffoldState()
val value: LocalDate
LaunchedEffect(key1 = true){
viewModel.eventFlow.collectLatest { event ->
when(event){
is AddEditTradeViewModel.UiEvent.ShowSnackbar ->{
scaffoldState.snackbarHostState.showSnackbar(
message = event.message
)
}
is AddEditTradeViewModel.UiEvent.SaveTrade -> {
navController.navigateUp()
}
}
}
}
Scaffold(
floatingActionButton = {
FloatingActionButton(
onClick = {
viewModel.onEvent(AddEditTradeEvent.SaveTrade)
},
backgroundColor = MaterialTheme.colors.primary
) {
Icon(imageVector = Icons.Default.Save, contentDescription = "Save Trade")
}
},
scaffoldState = scaffoldState
) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
Text(
modifier = Modifier.fillMaxWidth(),
text = "Enter Your Tradeeee",
style = MaterialTheme.typography.h4,
textAlign = TextAlign.Center
)
Spacer(modifier = Modifier.height(20.dp))
TransparentHintTextField(
text = tradesymbolState.text,
hint = tradesymbolState.hint,
onValueChange = {
viewModel.onEvent(AddEditTradeEvent.EnteredSymbol(it))
},
onFocusChange = {
viewModel.onEvent(AddEditTradeEvent.ChangeSymbolFocus(it))
},
isHintVisible = tradesymbolState.isHintVisible,
singleLine = true,
textStyle = MaterialTheme.typography.h5
)
Spacer(modifier = Modifier.height(16.dp))
TransparentHintTextField(
text = tradequantState.text,
hint = tradequantState.hint,
onValueChange = {
viewModel.onEvent(AddEditTradeEvent.EnteredQuantity(it))
},
onFocusChange = {
viewModel.onEvent(AddEditTradeEvent.ChangeQuantityFocus(it))
},
isHintVisible = tradequantState.isHintVisible,
textStyle = MaterialTheme.typography.body1,
//modifier = Modifier.fillMaxHeight()
)
Spacer(modifier = Modifier.height(16.dp))
TradeDatePicker(
value = LocalDate.now(),
onValueChanged = {
viewModel.onEvent(AddEditTradeEvent.EnteredTradeDate(it))
},
modifier = Modifier
.fillMaxWidth()
)
}
}
}
This is my viewmodel that I'm trying to update:
#HiltViewModel
class AddEditTradeViewModel #Inject constructor(
private val tradeUseCases: TradeUseCases,
savedStateHandle: SavedStateHandle
) : ViewModel(){
private val _tradeSymbol = mutableStateOf(
TradeTextFieldState(
hint = "Enter Trade Symobol"
))
val tradeSymbol: State<TradeTextFieldState> = _tradeSymbol
private val _tradeQuantity = mutableStateOf(
TradeTextFieldState(
hint = "Enter Number of Shares"
))
val tradeQuantity: State<TradeTextFieldState> = _tradeQuantity
private var _tradeDate : String? by remember {
mutableStateOf(null)
}
val tradeDate = { date: Long? ->
_tradeDate = date?.toString()?:""
}
private val _eventFlow = MutableSharedFlow<UiEvent>()
val eventFlow = _eventFlow.asSharedFlow()
private var currentTradeId: Int? = null
init{
savedStateHandle.get<Int>("tradeId")?.let { tradeId ->
if (tradeId != -1){
viewModelScope.launch {
tradeUseCases.getTrade(tradeId)?.also { trade ->
currentTradeId = trade.id
_tradeSymbol.value = tradeSymbol.value.copy(
text = trade.tradeSymb,
isHintVisible = false
)
_tradeQuantity.value = tradeQuantity.value.copy(
text = trade.tradequant,
isHintVisible = false
)
}
}
}
}
}
fun onEvent(event: AddEditTradeEvent){
when(event){
is AddEditTradeEvent.EnteredSymbol -> {
_tradeSymbol.value = tradeSymbol.value.copy(
text = event.value
)
}
is AddEditTradeEvent.ChangeSymbolFocus -> {
_tradeSymbol.value = tradeSymbol.value.copy(
isHintVisible = !event.focusState.isFocused &&
tradeSymbol.value.text.isBlank()
)
}
is AddEditTradeEvent.EnteredQuantity -> {
_tradeQuantity.value = tradeQuantity.value.copy(
text = event.value
)
}
is AddEditTradeEvent.ChangeQuantityFocus -> {
_tradeQuantity.value = tradeQuantity.value.copy(
isHintVisible = !event.focusState.isFocused &&
_tradeQuantity.value.text.isBlank()
)
}
is AddEditTradeEvent.EnteredTradeDate -> {
_tradeDate.value = tradeDate.value.copy(
text = event.value
)
}
is AddEditTradeEvent.SaveTrade -> {
viewModelScope.launch{
try {
tradeUseCases.addTrade(
Trade(
tradeSymb = tradeSymbol.value.text,
tradequant= tradeQuantity.value.text,
timestamp = System.currentTimeMillis(),
//tradestatus = "Long",
//color = MaterialTheme.colors.primary.green
id = currentTradeId
)
)
_eventFlow.emit(UiEvent.SaveTrade)
} catch (e: InvalidTradeException){
_eventFlow.emit(
UiEvent.ShowSnackbar(
message = e.message ?: "Couldn't save trade"
)
)
}
}
}
}
}
}
Lastly this is the event sealed class, not sure I'm using this correctly but in here for the "EnteredTradeDate" I declare it as a long and I'm not sure this is correct since I'm having trouble passing it from the screen to the viewmodel.
sealed class AddEditTradeEvent {
data class EnteredSymbol(val value: String): AddEditTradeEvent()
data class ChangeSymbolFocus(val focusState: FocusState): AddEditTradeEvent()
data class EnteredQuantity(val value: String): AddEditTradeEvent()
data class ChangeQuantityFocus(val focusState: FocusState): AddEditTradeEvent()
data class EnteredTradeDate(val value: Long) : AddEditTradeEvent()
data class ChangeDateFocus(val focusState: FocusState): AddEditTradeEvent()
object SaveTrade: AddEditTradeEvent()
}
I'm new to Jetpack Compose. I am currently developing a chat application. I ask the user to choose an image from the gallery or take a picture from the camera. Then I save the file Uri to the database and then listen to the list of all messages. When this list is updated, this image is recomposing and it flashes.
Messages list in viewmodel:
private var _messages = MutableStateFlow<List<ChatUiMessage>>(mutableListOf())
val messages: StateFlow<List<ChatUiMessage>> = _messages
...
private fun observeMessages() {
viewModelScope.launch {
chatManager.observeMessagesFlow()
.flowOn(dispatcherIO)
.collect {
_messages.emit(it)
}
}
}
Main chat screen:
...
val messages by viewModel.messages.collectAsState(listOf())
...
val listState = rememberLazyListState()
LazyColumn(
modifier = modifier.fillMaxWidth(),
reverseLayout = true,
state = listState
) {
itemsIndexed(items = messages) { index, message ->
when (message) {
...
is ChatUiMessage.Image -> SentImageBlock(
message = message
)
...
}
}
}
My SentImageBlock:
#Composable
private fun SentImageBlock(message: ChatUiMessage.Image) {
val context = LocalContext.current
val bitmap: MutableState<Bitmap?> = rememberSaveable { mutableStateOf(null) }
bitmap.value ?: run {
LaunchedEffect(Unit) {
launch(Dispatchers.IO) {
bitmap.value = try {
when {
Build.VERSION.SDK_INT >= 28 -> {
val source = ImageDecoder.createSource(context.contentResolver, message.fileUriPath.toUri())
ImageDecoder.decodeBitmap(source)
}
else -> {
MediaStore.Images.Media.getBitmap(context.contentResolver, message.fileUriPath.toUri())
}
}
} catch (e: Exception) {
null
}
}
}
}
Box(
modifier = Modifier
.fillMaxWidth()
.padding(end = 16.dp, top = 16.dp, bottom = 16.dp)
.heightIn(max = 200.dp, min = 200.dp)
) {
bitmap.value?.let {
Image(
bitmap = it.asImageBitmap(),
contentDescription = null,
modifier = Modifier
.wrapContentSize()
.align(Alignment.CenterEnd)
)
}
}
StandardText(text = message.sendFileStatus.toString())
StandardText(text = message.fileType.toString())
}
I have tried several things and either is the Image always blinking.
LazyColumn reuses views for items using key argument, and by default it's equal to item index. You can provide a correct key(something like message id) for views to be reused correctly:
val messages = listOf(1,2,3)
LazyColumn(
modifier = modifier.fillMaxWidth(),
reverseLayout = true,
state = listState
) {
itemsIndexed(
items = messages,
key = { index, message -> message.id }
) { index, message ->
when (message) {
...
is ChatUiMessage.Image -> SentImageBlock(
message = message
)
...
}
}
}
I have a LazyVerticalGrid with 2 cells.
LazyVerticalGrid(
cells = GridCells.Fixed(2),
content = {
items(moviePagingItems.itemCount) { index ->
val movie = moviePagingItems[index] ?: return#items
MovieItem(movie, Modifier.preferredHeight(320.dp))
}
renderLoading(moviePagingItems.loadState)
}
)
I am trying to show full width loading with LazyGridScope's fillParentMaxSize modifier.
fun LazyGridScope.renderLoading(loadState: CombinedLoadStates) {
when {
loadState.refresh is LoadState.Loading -> {
item {
LoadingColumn("Fetching movies", Modifier.fillParentMaxSize())
}
}
loadState.append is LoadState.Loading -> {
item {
LoadingRow(title = "Fetching more movies")
}
}
}
}
But since we have 2 cells, the loading can occupy half of the screen. Like this:
Is there a way my loading view can occupy full width?
Jetpack Compose 1.1.0-beta03 version includes horizontal span support for LazyVerticalGrid.
Here is the example usage:
private const val CELL_COUNT = 2
private val span: (LazyGridItemSpanScope) -> GridItemSpan = { GridItemSpan(CELL_COUNT) }
LazyVerticalGrid(
cells = GridCells.Fixed(CELL_COUNT),
content = {
items(moviePagingItems.itemCount) { index ->
val movie = moviePagingItems.peek(index) ?: return#items
Movie(movie)
}
renderLoading(moviePagingItems.loadState)
}
}
private fun LazyGridScope.renderLoading(loadState: CombinedLoadStates) {
if (loadState.append !is LoadState.Loading) return
item(span = span) {
val title = stringResource(R.string.fetching_more_movies)
LoadingRow(title = title)
}
}
Code examples of this answer can be found at: Jetflix/MoviesGrid.kt
LazyVerticalGrid has a span strategy built into items() and itemsIndexed()
#Composable
fun SpanLazyVerticalGrid(cols: Int, itemList: List<String>) {
val lazyGridState = rememberLazyGridState()
LazyVerticalGrid(
columns = GridCells.Fixed(cols),
state = lazyGridState
) {
items(itemList, span = { item ->
val lowercase = item.lowercase()
val span = if (lowercase.startsWith("a") || lowercase.lowercase().startsWith("b") || lowercase.lowercase().startsWith("d")) {
cols
}
else {
1
}
GridItemSpan(span)
}) { item ->
Box(modifier = Modifier
.fillMaxWidth()
.height(150.dp)
.padding(10.dp)
.background(Color.Black)
.padding(2.dp)
.background(Color.White)
) {
Text(
modifier = Modifier.align(Alignment.Center),
text = item,
fontSize = 18.sp
)
}
}
}
}
'
val names = listOf("Alice", "Bob", "Cindy", "Doug", "Ernie", "Fred", "George", "Harris")
SpanLazyVerticalGrid(
cols = 3,
itemList = names
)
Try something like:
var cellState by remember { mutableStateOf(2) }
LazyVerticalGrid(
cells = GridCells.Fixed(cellState),
content = {
items(moviePagingItems.itemCount) { index ->
val movie = moviePagingItems[index] ?: return#items
MovieItem(movie, Modifier.preferredHeight(320.dp))
}
renderLoading(moviePagingItems.loadState) {
cellState = it
}
}
)
The renderLoading function:
fun LazyGridScope.renderLoading(loadState: CombinedLoadStates, span: (Int) -> Unit) {
when {
loadState.refresh is LoadState.Loading -> {
item {
LoadingColumn("Fetching movies", Modifier.fillParentMaxSize())
}
span(1)
}
...
else -> span(2)
}
}
I have created an issue for it: https://issuetracker.google.com/u/1/issues/176758183
Current workaround I have is to use LazyColumn and implement items or header.
override val content: #Composable () -> Unit = {
LazyColumn(
contentPadding = PaddingValues(8.dp),
content = {
items(colors.chunked(3), itemContent = {
Row(horizontalArrangement = Arrangement.SpaceEvenly) {
val modifier = Modifier.weight(1f)
it.forEach {
ColorItem(modifier, it)
}
for (i in 1..(3 - it.size)) {
Spacer(modifier)
}
}
})
item {
Text(
text = stringResource(R.string.themed_colors),
style = MaterialTheme.typography.h3
)
}
items(themedColors.chunked(3), itemContent = {
Row(horizontalArrangement = Arrangement.SpaceEvenly) {
val modifier = Modifier.weight(1f)
it.forEach {
ColorItem(modifier, it)
}
for (i in 1..(3 - it.size)) {
Spacer(modifier)
}
}
})
})
}