How to preview LazyPagingItems/Paging 3 Library in Jetpack Compose - android-jetpack-compose

I think the title says it all.
LazyPagingItems constructor is internal. I can't pass LazyPagingItems as parameter in Preview Composable, let alone passing sample data. But I want to show preview of my composable, how should I do this?
#Composable
fun MainUi(users: LazyPagingItems<User>) {
Scaffold {
LazyColumn() {
items(users) {
// Rest of the code..
}
}
}
}
#Preview
#Composable
fun Preview() {
DefaultTheme {
MainUi(users = ) // How to pass sample data here?
}
}

You can use PagingData.empty() or PagingData.from(List<T>), like this:
#Preview
#Composable
fun Preview() {
DefaultTheme {
MainUi(users = flowOf(PagingData.from(listOf(User(..)))).collectAsLazyPagingItems()
}
}
I'm not sure if the items are shown in the preview, but at least you get to render it...

Related

Access scoped viewModel from TopAppBar in jetpack compose

Folks I am stuck engineering a proper solution to access a viewModel scoped to a nav graph , from a button that exists in the TopAppBar in a compose application
Scaffold{
TopAppBar-> Contains the Save Button
Body->
BioDataGraph() -> Contains 5 screens to gather biodata information , and a viewmodel scoped to the graph
}
}
My BioDataViewModel looks like this
class BioDataViewModel{
fun gatherPersonalInformation()
fun gatherPhotos()
...
fun onSaveEverything()
}
The issue i came across is as i described above , how should i go about access the BioDataViewModel , such that i can invoke onSaveEverything when save is clicked in the TopAppBar.
What I have tried
private val performSave by mutableStateOf(false)
Scaffold(
topBar = {
TopAppBar(currentDestination){
//save is clicked.
performSave = true
}
})
{
NavHost(
navController = navController,
startDestination = homeNavigationRoute,
modifier = Modifier
.padding(padding)
.consumedWindowInsets(padding),
) {
composable(route = bioDataRoute) {
val viewModel = hiltViewModel<BioDataViewModel>()
if (performSave){
viewModel.onSaveEverything()
}
BioDataScreen(
viewModel
)
}
}
}
The problem with the approach above is that how and when should i reset the state of performSave ? . Because if i do not set it to false; on every recomposition onSaveEverything would get called.
What would be the best way to engineer a solution for this ? . I checked to see if a similar situation was tackled in jetpack samples , but i found nothing there .
I'm not sure if I understand you correctly, but you can define the BioDataViewModel in activity level, and you can access it in the TopAppBar like this
class MyActivity: ComponentActivity() {
// BioDataViewModel definition here
private val viewModel: BioDataViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Scaffold(
topBar = {
TopAppBar(currentDestination) {
//save is clicked.
viewModel.onSaveEverything() // call onSaveEverything here
}
})
{
...
...
}
...
...
Edit:
If you want to have the same instance of ViewModel from activity and NavGraph level, you can consider this, a reference from my other answer.
You can define the ViewModelStoreOwner in the navigation graph level.
NavHost(
navController = navController,
startDestination = homeNavigationRoute,
modifier = Modifier
.padding(padding)
.consumedWindowInsets(padding),
) {
val viewModelStoreOwner = checkNotNull(LocalViewModelStoreOwner.current) {
"LocalViewModelStoreOwner not available"
}
composable(route = bioDataRoute) {
val viewModel = hiltViewModel<BioDataViewModel>(viewModelStoreOwner)
if (performSave){
viewModel.onSaveEverything()
}
BioDataScreen(
viewModel
)
}
}

Ui is not updating from viewmodel kotlin flow

I am quite new in Android Flow and JetPack compose,
I am trying to update my UI when mutable state is being changed , but this is not calling our composable , here is my code
#Composable
fun Grid() {
val mainViewModel by viewModels<DashBoardViewModel>()
mainViewModel.getData()
when (val result = mainViewModel.mutableState.value) {
is Resource.Success -> {
LazyVerticalGrid(
cells = GridCells.Adaptive(100.dp)
) {
items(result.device.items.first().devices.count()) {
EachItem(it)
}
}
}
is Resource.Error -> { Text(text = result.message) }
Resource.Loading -> { CircularProgressIndicator() }
Resource.Empty -> {}
else -> { CircularProgressIndicator() }
}
}
ViewModel:
#HiltViewModel
class DashBoardViewModel #Inject constructor(
private val dashBoardRepository: DashBoardRepository
) : ViewModel() {
val mutableState = MutableLiveData<Resource>()
fun getData() = viewModelScope.launch {
flow {
emit(Resource.Loading)
try {
val mResponse = dashBoardRepository.getDevice()
emit(Resource.Success(mResponse))
} catch (e: Exception) {
e.printStackTrace()
emit(Resource.Error("Error"))
}
}.flowOn(Dispatchers.IO).collect {
mutableState.value = it
}
}
}
There are two problems in your code:
mainViewModel.mutableState.value gets only the current value from your mutable state. Your composable will not be notified when this value changes and thus it cannot reflect the change. If you want to use LiveData in your viewmodel, you have to use observeAsState() extension function which converts LivaData to State that can be automatically observed by composable function. Other option is to have (Mutable)State directly in your viewmodel. See this state explanation.
Your mainViewModel.getData() function will be called every time your Grid() function recomposes, which will be every time your mainViewModel.mutableState changes (once you observe it correctly). You definitely don't want that. Better solution would be to call getData() from your viewModel's init block, or, if you really need to call it from your composable function, use LaunchedEffect.
And, as a side note, the way you are creating flow and then collecting it into LiveData is really odd and unnecessary. You can do something like this instead:
fun getData() = viewModelScope.launch {
mutableState.value = Resource.Loading
try {
val mResponse = dashBoardRepository.getDevice()
mutableState.value = Resource.Success(mResponse)
} catch (e: Exception) {
e.printStackTrace()
mutableState.value = Resource.Error("Error")
}
}

How to pass a composable content parameter in data class

I need to pass a compose content parameter in data class. For example a button can render when added into this content.
data class ContentData {
val content: #Composable ()-> Unit
}
This is working but when I get the app background I am getting parcelable exception. How to solve this problem.
One possible explanation I think that will occur related with a parcelable error, happens if you try to pass such object between activities as extras through Intent. Consider not use Composable as parameters in objects. Instead, try to represent the parameters of your Composable with a model which contains the parameters.
// your compose function
#Composable
fun Item(content: String = "Default", padding: Dp){
// ...
}
// Ui Model which contains your data (instead of have a weird composable reference) as a parcelable.
data class ContentData(
val content: String = "Default",
val paddingRaw: Int = 0
) : Parcelable {
constructor(parcel: Parcel) : this(
parcel.readString().orEmpty(),
parcel.readInt()
) {
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(content)
parcel.writeInt(paddingRaw)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<ContentData> {
override fun createFromParcel(parcel: Parcel): ContentData {
return ContentData(parcel)
}
override fun newArray(size: Int): Array<ContentData?> {
return arrayOfNulls(size)
}
}
}
// Example if you need the model between activities through the intent as an extra.
val data = ContentData("your content", 11)
val intent = Intent().apply {
putExtra("keyContentData", data)
}
//The way of get and use your model.
val contentData = intent.extras?.get("keyContentData") as ContentData
#Composable
fun ParentComponent(){
// ...
Item(
contentData?.content.orEmpty(),
contentData?.paddingRaw?.dp ?: 0.dp
)
// ...
}

Jetpack Compose:composable that have too many composables can cause the lazyColumn get stuck when it scroll

I want a graph like this.
The graph is composable in LazyColumn's item. And the lazyColumn get stuck when the graph shows and hides. How to optimize it?
code:
#Composable
fun List(){
LazyColumn(){
item{
Graph()
}
items{
// other items
}
}
}
#Composable
fun Graph(){
Row(Modifer.height(200.dp)) {
// other simple Composables,
if(isShow)
repeat(30){
Column() {
repeat(7) {
Box(Modifer.size(16.dp).background(Blue))
}
}
}
}
}
I don't know much about compose.I found a way to fixme it.
Use Side-effect.
#Composable
fun List(){
val isShow = remember { mutableStateOf(true) }
val listState = rememberLazyListState()
LaunchedEffect(listState) {
// Graph is first item
snapshotFlow { listState.firstVisibleItemIndex }.collect {
if (it != 0) {
isShow.value = false
}else{
isShow.value = true
}
}
}
LazyColumn(state = listState){
item{
Graph(isShow.value)
}
items{
// other items
}
}
}
#Composable
fun Graph(isShow:Boolean){
Row(Modifer.height(200.dp)) {
// other simple Composables,
if(isShow)
repeat(30){
Column() {
repeat(7) {
Box(Modifer.size(16.dp).background(Blue))
}
}
}
}
}
There are simple codes.
Is a good way?
You need to use LazyColumn's items(...) or item { ..composable..} to take advantage of its lazy-ness. For each item on your column, you need to surround it with item to tell compose that is a row inside that column.

Jetpack Compose - How can we call a #Composable function inside an onClick()

I know its not possible to call composable functions inside onClick.
#Composable invocations can only happen from the context of a #Composable function
Compose version - alpha06
But I'm stuck with the below requirement.
The requirement is,
Call a server api call inside an onClick.
LazyColumnFor(items = list) { reports ->
Box(Modifier.clickable(
onClick = {
//API call
val liveDataReportsDetails =
viewModel.getReportDetails("xxxx")
LiveDataComponentForReportsDetails(liveDataReportsDetails)
}
)) {
ReportListItem(
item = reports
)
}
}
So you're right, composable functions cannot be called from within onClicks from either a button or a modifier. So you need to create a value like:
private val showDialog = mutableStateOf(false)
When set to true you want to invoke the composable code, like:
if(showDialog.value) {
alert()
}
Alert being something like:
#Composable
fun alert() {
AlertDialog(
title = {
Text(text = "Test")
},
text = {
Text("Test")
},
onDismissRequest = {
},
buttons = {
Button(onClick = { showDialog.value = false }) {
Text("test")
}
}
)
}
Now finish with changing the boolean where intended, like:
Box(Modifier.clickable(
onClick = {
showDialog.value = true
}
))
I hope this explanation helps, of course the value doesn't have to be a boolean, but you get the concept :).

Resources