The last elements are not visible in LazyColumn. Jetpack Compose - android

I take an example from this codelab
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
return ComposeView(requireContext()).apply {
setContent {
//Column(modifier = Modifier.background(Color.LightGray)) {
//Text(text = "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!", modifier = Modifier.weight(1f))
val scrollState = rememberLazyListState()
LazyColumn(state = scrollState, modifier = Modifier
.background(Color.Cyan).fillMaxWidth()) {
items(100) {
Text("Item #$it")
}
}
//}
}
}
}
But latest element not visible on my scree. P.S I scrolled to the end
When add height in column - the last elements are visible, but LazyColumn stretches over the entire height
LazyColumn(state = scrollState, modifier =
Modifier.background(Color.Cyan).fillMaxWidth().height(350.dp)) {
items(100) {
Text("Item #$it")
}
}
This happens because the column is a root element. Ok, Adding Column() in root element and LazyColumn() into in root, but I want the LazyСolumn to take up the entire height
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
return ComposeView(requireContext()).apply {
setContent {
Column(modifier = Modifier.background(Color.LightGray)) {
val scrollState = rememberLazyListState()
LazyColumn(state = scrollState, modifier = Modifier
.background(Color.Cyan).fillMaxWidth()/*.height(350.dp)*/) {
items(100) {
Text("Item #$it")
}
}
}
}
}
}
The last elements are not visible
Ideally I want to make a scrolling table with a static header
UPD:
When I add Text() On and Under LazyColumn(), also the last elements are not visible
setContent {
MaterialTheme {
Column(modifier = Modifier
.background(Color.LightGray)
.fillMaxSize()) {
Text(text = "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!", modifier = Modifier.background(Color.Red).weight(1f)
)
val scrollState = rememberLazyListState()
LazyColumn(
state = scrollState,
modifier = Modifier
.background(Color.Cyan)
.fillMaxWidth()
.weight(4f)
) {
items(100) {
Text("Item #$it")
}
}
Text(text = "!!!!!!!!!!!!!!!!!!!!", modifier = Modifier.background(Color.Red).weight(1f))
}
}
}

Wrap your LazyColumn with Scaffold and set contentPadding for the LazyColumn with the padding values provided by the Scaffold:
Scaffold { paddingValues ->
LazyColumn(
modifier = Modifier.fillMaxSize(),
contentPadding = paddingValues
) {
...
}
}

Try this:
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
return ComposeView(requireContext()).apply {
setContent {
Box(modifier = Modifier.background(Color.LightGray)) {
val scrollState = rememberLazyListState()
LazyColumn(state = scrollState, modifier = Modifier
.background(Color.Cyan).fillMaxSize()) {
items(100) {
Text("Item #$it")
}
}
}
}
}
}

Replacing:
val scrollState = rememberLazyListState()
with:
val scrollState = rememberLazyListState(99 /*or use (list.size - 1) if you have a list in you LazyColumn */)
worked perfectly for me.

This looks like the bottom of your view is under the navigation bar. Check out if you have this line in your activity:
WindowCompat.setDecorFitsSystemWindows(window, false)
You can remove this line if you don't need to place your views under navigation bar.
If you need it, like to make it transparent and place some background view underneath, you can use Accompanist Insets. With this library you can add padding respectful to navigation/status bar, like with Modifier.navigationBarsPadding():
LazyColumn(
state = scrollState,
modifier = Modifier
.background(Color.Cyan)
.fillMaxWidth()
.navigationBarsPadding()
) {
items(100) {
Text("Item #$it")
}
}

Related

Navigate back with Compose in Fragment with NavGraph

I need to go back from RegisterUserScreen to Login
class RegisterUser : Fragment() {
private val viewModel: RegistrationViewModel by viewModels()
#SuppressLint("InflateParams")
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View {
return ComposeView(requireContext()).apply {
setContent {
RegisterScreen(viewModel, this#RegisterUser)
}
}
}
}
In RegisterScreen I have Icon with clickable property:
Icon(
painter = painterResource(id = R.drawable.ic_baseline_arrow_back_24),
modifier = Modifier
.align(Alignment.Start)
.width(40.dp)
.height(30.dp)
.clickable { findNavController(registerUserFragment).navigate(R.id.action_registerUser_to_logIn) },
contentDescription = stringResource(id = R.string.register_user_button_back)
)
Problem is when I click on it nothing happens.
EDIT:
Failed to open APK
'/data/app/~~Jg0TKmfn-xZKpM84xgQqlw==/android.tvz.hr.fitnessclienttracker-DRhW5UElzP1jmYnzMDYMpQ==/base.apk':
I/O error 2022-12-22 20:37:16.666 742-742 ndroid.systemu
pid-742 E Failed to open APK
'/data/app/~~Jg0TKmfn-xZKpM84xgQqlw==/android.tvz.hr.fitnessclienttracker-DRhW5UElzP1jmYnzMDYMpQ==/base.apk':
I/O error 2022-12-22 20:37:16.667 742-742 ResourcesManager
pid-742 E failed to add asset path
'/data/app/~~Jg0TKmfn-xZKpM84xgQqlw==/android.tvz.hr.fitnessclienttracker-DRhW5UElzP1jmYnzMDYMpQ==/base.apk'
java.io.IOException: Failed to load asset path
/data/app/~~Jg0TKmfn-xZKpM84xgQqlw==/android.tvz.hr.fitnessclienttracker-DRhW5UElzP1jmYnzMDYMpQ==/base.apk

Scroll issue with LazyColumn inside BottomSheetDialogFragment

I use LazyColumn inside BottomSheetDialogFragment, but if to scroll LazyColumn list UP then Bottom Sheet Dialog scrolls instead of LazyColumn list. Seems like BottomSheetDialogFragment intercepts user touch input.
That's how it looks:
How to properly use LazyColumn inside BottomSheetDialogFragment?
MyBottomSheetDialogFragment.kt:
class MyBottomSheetDialogFragment : BottomSheetDialogFragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
return ComposeView(requireContext()).apply {
setContent {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text("Header", color = Color.Black)
LazyColumn(
Modifier
.weight(1f)
.fillMaxWidth()) {
items(100) {
Text("Item $it", Modifier.fillMaxWidth(), Color.Black)
}
}
}
}
}
}
}
And show it using this code:
MyBottomSheetDialogFragment().show(activity.supportFragmentManager, null)
When we used the XML RecyclerView list, to fix this issue we had to wrap the RecyclerView list with NestedScrollView like described here, but how to fix it with Jetpack Compose?
Since compose 1.2.0-beta01 problem can be solved by using
rememberNestedScrollInteropConnection:
Modifier.nestedScroll(rememberNestedScrollInteropConnection())
In my case BottomSheetDialogFragment is standard View and it has ComposeView with id container. In onViewCreated I do:
binding.container.setContent {
AppTheme {
Surface(
modifier = Modifier.nestedScroll(rememberNestedScrollInteropConnection())
) {
LazyColumn {
// ITEMS
}
}
}
}
And now the list is scrolling in a correct way.
You might give a try to this https://gist.github.com/chrisbanes/053189c31302269656c1979edf418310.
This is a workaround for https://issuetracker.google.com/issues/174348612, which means that nested scrolling layouts in Compose do not work as nested scrolling children in the view system.
Sample usage in your case :
class MyBottomSheetDialogFragment : BottomSheetDialogFragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
return ComposeView(requireContext()).apply {
setContent {
Surface(
modifier = Modifier.nestedScroll(rememberViewInteropNestedScrollConnection())
){
LazyColumn(
Modifier
.weight(1f)
.fillMaxWidth()) {
items(100) {
Text("Item $it", Modifier.fillMaxWidth(), Color.Black)
}
}
}
}
}
}
}
If you’re using compose within the activity that launched the bottom sheet dialog fragment, you might be better off simply sticking with a purely compose implementation and leveraging the compose equivalent bottom sheet component: ModalBottomSheetLayout
https://developer.android.com/reference/kotlin/androidx/compose/material/package-summary#ModalBottomSheetLayout(kotlin.Function1,androidx.compose.ui.Modifier,androidx.compose.material.ModalBottomSheetState,androidx.compose.ui.graphics.Shape,androidx.compose.ui.unit.Dp,androidx.compose.ui.graphics.Color,androidx.compose.ui.graphics.Color,androidx.compose.ui.graphics.Color,kotlin.Function0)
I found an excellent answer to this issue. We can use Jetpack Compose bottom sheet over Android view using Kotlin extension.
More details about how it works are here.
Here is all code we need:
// Extension for Activity
fun Activity.showAsBottomSheet(content: #Composable (() -> Unit) -> Unit) {
val viewGroup = this.findViewById(android.R.id.content) as ViewGroup
addContentToView(viewGroup, content)
}
// Extension for Fragment
fun Fragment.showAsBottomSheet(content: #Composable (() -> Unit) -> Unit) {
val viewGroup = requireActivity().findViewById(android.R.id.content) as ViewGroup
addContentToView(viewGroup, content)
}
// Helper method
private fun addContentToView(
viewGroup: ViewGroup,
content: #Composable (() -> Unit) -> Unit
) {
viewGroup.addView(
ComposeView(viewGroup.context).apply {
setContent {
BottomSheetWrapper(viewGroup, this, content)
}
}
)
}
#OptIn(ExperimentalMaterialApi::class)
#Composable
private fun BottomSheetWrapper(
parent: ViewGroup,
composeView: ComposeView,
content: #Composable (() -> Unit) -> Unit
) {
val TAG = parent::class.java.simpleName
val coroutineScope = rememberCoroutineScope()
val modalBottomSheetState = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden)
var isSheetOpened by remember { mutableStateOf(false) }
ModalBottomSheetLayout(
sheetBackgroundColor = Color.Transparent,
sheetState = modalBottomSheetState,
sheetContent = {
content {
// Action passed for clicking close button in the content
coroutineScope.launch {
modalBottomSheetState.hide() // will trigger the LaunchedEffect
}
}
}
) {}
BackHandler {
coroutineScope.launch {
modalBottomSheetState.hide() // will trigger the LaunchedEffect
}
}
// Take action based on hidden state
LaunchedEffect(modalBottomSheetState.currentValue) {
when (modalBottomSheetState.currentValue) {
ModalBottomSheetValue.Hidden -> {
when {
isSheetOpened -> parent.removeView(composeView)
else -> {
isSheetOpened = true
modalBottomSheetState.show()
}
}
}
else -> {
Log.i(TAG, "Bottom sheet ${modalBottomSheetState.currentValue} state")
}
}
}
}

How to use signature pad library with jetpack compose using Android and Kotlin?

I was using this library to get signature https://github.com/gcacace/android-signaturepad with Android app written in Kotlin, now I moved to jetpack compose and I want to use it with the new UI library
my old code was:
XML
<com.github.gcacace.signaturepad.views.SignaturePad
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/signature_pad"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:penColor="#android:color/black"
/>
Fragment
class SignatureFragment : Fragment() {
private lateinit var signatureBinding: FragmentSignatureBinding
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
setupSignatureBinding(inflater, container)
handleSignatureClicks()
return signatureBinding.root
}
private fun setupSignatureBinding(inflater: LayoutInflater, container: ViewGroup?) {
signatureBinding = DataBindingUtil.inflate(
inflater,
R.layout.fragment_signature,
container,
false
)
}
private fun handleSignatureClicks() {
signatureBinding.signaturePad.setOnSignedListener(object : SignaturePad.OnSignedListener {
override fun onStartSigning() {}
override fun onSigned() {
signatureBinding.saveButton.isEnabled = true
signatureBinding.clearButton.isEnabled = true
}
override fun onClear() {
signatureBinding.saveButton.isEnabled = false
signatureBinding.clearButton.isEnabled = false
}
})
signatureBinding.clearButton.setOnClickListener{ signatureBinding.signaturePad.clear() }
signatureBinding.saveButton.setOnClickListener {
val signatureBitmap: Bitmap = signatureBinding.signaturePad.signatureBitmap
}
}
}
I was thinking about solving this problem and I found that I could go from the compose screen to activity with intent, and I did that at the code below, but the problem is I can't back to the form to complete the rest of the questions but instead when clicking on the back button it goes to the main activity of the app
#Composable
fun SignatureQuestion(question: QuestionModel, formViewModel: FormViewModel) {
val context = LocalContext.current
CustomInputFieldContainer(
isRequired = question.is_required,
label = question.question_title
) {
Button(
onClick = {
val intent = Intent(context, SignatureActivity::class.java)
intent.putExtra("question", question)
context.startActivity(intent)
},
contentPadding = PaddingValues(),
modifier = Modifier.background(Color.Yellow)
) {
Row(
modifier = Modifier
.fillMaxWidth()
.wrapContentSize(Alignment.BottomCenter)
.padding(vertical = 10.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(imageVector = Icons.Filled.Edit, contentDescription = null)
Spacer(modifier = Modifier.width(8.dp))
Text(text = "Add Signature")
}
}
}
}
Could anyone help me solve this problem?
I have been working on a updated version of this library since the original author don't seems to have no time to do it. Find updated version with Jetpack Compose support here: https://github.com/warting/android-signaturepad
If you want to access xml layout then follow below code as i did with your mentioned library:
1st create xml layout with your library tag.
and then write below lines in your #composable function:
val parser: XmlPullParser = context.resources.getLayout(R.layout.test_layout)
try {
parser.next()
parser.nextTag()
} catch (e: Exception) {
e.printStackTrace()
}
val attr: AttributeSet = Xml.asAttributeSet(parser)
val signature= remember {
SignaturePad(context,attr).apply {
id=R.id.signature_pad
}
}
AndroidView({signature}) { signaturepad->
// here you can play with signature pad
}

Convert XML ShimmerFrameLayout into Composable function

i'm new in android jetpack compose
i would like to implement Shimmer effect for Android. as per given in this documentation
it's working fine with xml approach, but i want to do same with compose function (in short embed XML into composable function).
Here is XML Code: shimmer_view.xml
<com.facebook.shimmer.ShimmerFrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context=".presentation.ui.recipe_list.UserListFragment"
android:id="#+id/shimmerFrameLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:shimmer_auto_start="true"
>
<include layout="#layout/shimmer_placeholder_card" />
</com.facebook.shimmer.ShimmerFrameLayout>
Fragment where i want to use above xml file
class UserListFragment: Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
// Traditional Approach Working Fine.
// val view = inflater.inflate(R.layout.shimmer_view, container, false)
// return view
// ComposeView inside fragment
val composeView = ComposeView(requireContext()).apply {
setContent {
Text(text = "Welcome in Compose-World ")
// Here i want To use xml file as a Compose View
}
}
return composeView
}
}
is it possible to inflate or Convert shimmer_view.xml into composable function?
OR
somehow emmbed this xml into compose function.
for reference please share sample code if any. it will help us.
Thank you
I created this function that help me to inflate an XML layout as a composable instance:
#Composable
fun createAndroidViewForXMLLayout(#LayoutRes resId: Int) {
val context = LocalContext.current
val your_xml_Layout = remember(resId, context) {
LayoutInflater.from(context).inflate(resId,null)
}
AndroidView({ your_xml_Layout })
}
In your case, you just have to call this function like here:
class UserListFragment: Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val composeView = ComposeView(requireContext()).apply {
setContent {
Text(text = "Welcome in Compose-World ")
// Here you add the function with the id of your layout **"R.layout.shimmer_view"**
createAndroidViewForXMLLayout(R.layout.shimmer_view)
}
}
return composeView
}
}
Here's a composable that gives you the shimmer effect inside another composable:
#Composable
fun Shimmer(modifier: Modifier = Modifier, content: #Composable () -> Unit) {
val context = LocalContext.current
val shimmer = remember {
ShimmerFrameLayout(context).apply {
addView(ComposeView(context).apply {
setContent(content)
})
}
}
AndroidView(
modifier = modifier,
factory = { shimmer }
) { it.startShimmer() }
}
And use it as:
Shimmer {
// Your placeholder
Box(modifier = Modifier.size(100.dp).background(Color.LightGray))
}

Jetpack Compose - LazyColumnFor not recomposing when item added

I am trying to create a list of items in Jetpack Compose that automatically updates whenever the underlying data changes, but it is not updating and I cannot add an item to the LazyColumnFor after it has initially composed.
private var latestMessages = mutableListOf<ChatMessage>()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
return ComposeView(context = requireContext()).apply {
setContent {
LatestMessages(latestMessages)
}
}
}
#Composable
fun LatestMessages(messages: MutableList<ChatMessage>) {
Column {
LazyColumnFor(items = messages) { chatMessage ->
LatestMessageItem(chatMessage) {
// onclick
}
}
}
Box(alignment = Alignment.Center) {
Button(onClick = {
latestMessages.add(
ChatMessage(
"testId",
"testText2",
"testFromId2",
"testToId",
"timestamp",
0
)
)
}) {
}
}
}
The Button in the Box is just to test adding an item. From what I understand LazyColumnFor should update whenever the underlying data is changed, similar to ForEach in SwiftUI. What am I doing wrong?
Edit March 2022
Jetpack compose no longer uses LazyColumnFor so I've updated the answer to reflect the current state of compose.
Jetpack compose always needs a state object to be modified before recomposition can occur. You need to use a MutableStateList<T>. You can pass a reference to it around and update in any method which accepts a SnapShotStateList<T>
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
return ComposeView(context = requireContext()).apply {
setContent {
val latestMessages = mutableStateListOf<ChatMessage>()
LatestMessages(latestMessages)
}
}
}
#Composable
fun LatestMessages(messages: SnapshotStateList<ChatMessage>) {
Column {
LazyColumn {
items(messages){ chatMessage ->
LatestMessageItem(chatMessage) {
// onclick
}
}
}
Box(alignment = Alignment.Center) {
Button(onClick = {
latestMessages.add(
ChatMessage(
"testId",
"testText2",
"testFromId2",
"testToId",
"timestamp",
0
)
)
}) {
}
}
}

Categories

Resources