What is the recommended solution for creating a NumberPicker Widget in Jetpack Compose? Similar to the image below. I am able to create an NumberPicker using an AndroidView within my composable but the view does not seem to allow flings or snap to position. Btw the UI below shows three NumberPickers placed in a row. It is not supposed to represent a DatePicker
By coincidence I've implemented a screen like that last week.
I can't share the whole code here, but basically what I did was:
Create a layout with a DatePicker (res/layout/date_picker.xml).
<DatePicker xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/datePicker"
android:theme="#style/DatePickerStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:calendarViewShown="false"
android:datePickerMode="spinner" />
Then, use it in your composable function.
#Composable
fun DatePicker(
onDateSelected: (Date) -> Unit
) {
AndroidView(
modifier = Modifier.fillMaxWidth(),
factory = { context ->
val view = LayoutInflater.from(context).inflate(R.layout.date_picker, null)
val datePicker = view.findViewById<DatePicker>(R.id.datePicker)
val calendar = Calendar.getInstance() // show today by default
datePicker.init(
calendar.get(Calendar.YEAR),
calendar.get(Calendar.MONTH),
calendar.get(Calendar.DAY_OF_MONTH)
) { _, year, monthOfYear, dayOfMonth ->
val date = Calendar.getInstance().apply {
set(year, monthOfYear, dayOfMonth)
}.time
onSelectedDateUpdate(date)
}
datePicker
}
)
}
Finally, use it in a ModalBottomSheetLayout
Editing my answer... Using a NumberPicker working as well...
AndroidView(
modifier = Modifier.fillMaxWidth(),
factory = { context ->
NumberPicker(context).apply {
setOnValueChangedListener { numberPicker, i, i2 -> }
minValue = 0
maxValue = 50
}
}
)
Here is the result.
I know maybe you are not looking for something like this. But since there is no such widget in compose yet and compose is all about making your way easier to build your own component. So Apart from android.widget NumberPicker, you can make something like this one. You can change the visualization more like the NumberPicker widget and add your callback and stuff.
Have you checked this one on github? ComposeNumberPicker.Kt
We are using this library in our compose project for number picker widget.
https://github.com/ChargeMap/Compose-NumberPicker
Related
I got a two states for handling a dynamic pop up screen component
var showPopUpScreen by remember { viewModel.popUpScreenIsOpen }
var popUpType by remember { viewModel.popUpScreenType }
but when I change the value of these mutableState-values when opening the pop up component
like this:
fun OpenPopUpScreen(type: BasePopUpScreen) {
popUpScreenType.value = type
popUpScreenIsOpen.value = true
}
will this composable function get executed twice (which is performance heavy) or will it be smart enough to know that these values are set at once so execute my pop up render function only once?
Extra code info:
fun LiveTrainingScreen(viewModel: LiveTrainingViewModel = viewModel()) {
// lots of code and then:
var showPopUpScreen by remember { viewModel.popUpScreenIsOpen }
var popUpType by remember { viewModel.popUpScreenType }
//pop up container
if(showPopUpScreen) {
Row(modifier = Modifier
.fillMaxSize()
.background(Color.Black.copy(alpha = 0.6f))
.zIndex(11f), verticalAlignment = Alignment.CenterVertically) {
Column(modifier = Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
DyanmicPopUpScreenLiveTraining(popUpScreenTypeInfo = popUpType, viewModel = viewModel)
} // pop up main column
} // end pop up screen row
} // end if pop up screen
}
I believe the recomposition starts right after both have changed as the compose guide says:
"Recomposition is optimistic and may be canceled." [source: https://developer.android.com/jetpack/compose/mental-model]
in which means the recomposition will be canceled as the other parameter is assigned and you will see the change in state in UI in means of both values.
However, it is a better approach to save the UI state inside a data class and remember the data class directly. that way, you change both variables and the composition resets as the data class changes. plus, rather than remembering the data class, hoist the state in a ViewModel and you will good to go.
I think compose is smart enough to identify the changes and react on it.
As per your question
once you set first value it will start changes compose views which are dependent on it
And suppose considering complex view previous recomposition process is going on, after setting second value previous recomposition will get cancelled and compose will recompose your screen with updated both values.
So effectively we can Recomposition will happen once only.
Please try to tolerate my inexperience.
I'm practicing Jetpack Compose navigation. I making an app that displays a set of card in a LazyHorizontalGrid format, and when clicked it navigates to a screen (destination) containing details of the card, depending on which card is clicked.
Here's what I mean:
I want to navigate to a screen (destination) that describe a particular "Favorite Collections" when clicked, depending on which is clicked.
I was following a tutorial on youtube, but I got lost when he was using Companion Object on a new activity (i.e having multiple activity in a single project and navigating by intent), since that's not recommended I'm not doing that, so I don't know where to put my Companion Object.
RecyclerView in Jetpack Compose - Jetpack Compose For Beginners #7
As I get errors when I try to put the Companion Object in the file of the Screen I'm trying to navigate to, error; Modifier 'companion' is not applicable inside 'file', or In a Composable in the file, then I get this error; Unresolved reference: companion.
This is the screen I'm trying to navigate to (let's call it; DetailScreen);
#Composable
fun DisplayHomeInfo(){
WelcomeText()
HomeInfoDetails(homeInfo = )
}
#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,
) {
....
This is the screen I'm trying to navigate from;
#Composable
fun HomeScreen(onHomeCardClick: () -> Unit) {
HomeContentScreen(onHomeCardClick = onHomeCardClick)
}
....
Data class I want to load on the detail screen;
data class HomeInfo(
val id: Int,
val title: String,
val sex: String,
val age: Int,
val description: String,
val homeInfoImageId: Int = 0
)
And this is the Object I want to match with the data;
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
),
...
)
}
To summarize everything, MY QUESTION is how do I pass the data to the detailScreen, and show each card details, depending on the card clicked.
Please I understand my explanation is not very clear, and I'll be more than happy to clarify anything.
No information is too small, I will appreciate any help. Thanks a lot in advance.
I have a project where we display a graph, this graph is in an Box which is scrollable.
By opening the view, we need to center the root node which causes the problem currently.
Determining the position and setting the values of the states is currently done the following way:
.onGloballyPositioned { coordinates -> scrollBy = coordinates.positionInParent().y - dpstate!!.scrollState.firstVisibleItemScrollOffset
}
.onFocusChanged {
if(it.isFocused ){
print("is focused")
scope.launch { dpstate!!.scrollState.animateScrollBy(scrollBy)
dpstate!!.offsetState.value = Offset(leftX.toFloat(),dpstate!!.offsetState.value.y)
}
}
}
in the modifier of the box.
The state dpstate is an instance of the following:
data class DisplayState(
val scrollState: LazyListState,
val scaleState: MutableState<Float>,
val offsetState: MutableState<Offset>,
val editState: MutableState<Boolean>,
val showInfo:Map<Int, MutableState<Boolean>>,)
Important is, that I need to center by opening it, not by clicking a button.
My try was in the calling code of all of this the following code:
DisposableEffect(Unit){
com.github.tukcps.appel.ui.rendering.focusRequester!!.requestFocus()
onDispose { }
}
There is no exception, it just don't do anything, thanks for your help.
I'm using the code below to display AdMob Ads, but unfortunately it's not showing anything. Please let me know if I have missed something
#Composable
fun AdvertView(modifier: Modifier = Modifier) {
AndroidView(
modifier = modifier.fillMaxWidth(),
factory = { context ->
AdView(context).apply {
adSize = AdSize.BANNER
adUnitId = "ca-app-pub-3940256099942544/6300978111"
loadAd(AdRequest.Builder().build())
}
},
update = {
it.apply {
loadAd(AdRequest.Builder().build())
}
}
)
}
I got a similar problelm.. What worked for me was making sure I had internet connection and waiting a bit for the banner ad to load when I run my code..
Also Since Admob 21.0.0, you would get the following error:
Val cannot be reassigned
So, instead of
adSize = AdSize.BANNER
use
setAdSize(AdSize.BANNER)
AdMob in Jetpack Compose (Banner and Interstitial) enter link description here
I have a nested Recycler lists which don't have ~~scrolling~~ & nestedScrollingEnabled=false. I'm attempting to swipe up and click on the recycler item by it's text. Having issues with determining when to swipe and how far.
UPDATE: This may have scrolling, I may need to specify the ViewHolder of the Item with text instead of the view with text... Experimenting...
parent_recycler_list
recycler_list
List item A
List item B
recycler_list
List item A
List item B
So far I am able to find the item and try to click on it:
Espresso.onView(
CoreMatchers.allOf(
ViewMatchers.withId(R.id.recycler_list),
ViewMatchers.hasDescendant(recyclerViewItemWithText(text))
)
).perform(
RecyclerViewActions.actionOnItem<RecyclerView.ViewHolder>(
recyclerViewItemWithText(text),
ViewActions.click()
)
)
fun recyclerViewItemWithText(text: String) = object : BoundedMatcher<View, View>(View::class.java) {
override fun describeTo(description: Description?) {
description?.appendText("Searching for text with: $text")
}
override fun matchesSafely(item: View?): Boolean {
val views = ArrayList<View>()
item?.findViewsWithText(views, text, View.FIND_VIEWS_WITH_TEXT)
return when (views.size) {
1 -> true
else -> false
}
}
}
This only works by it self when the list item is displayed.
I have tried to swipe until the view item is displayed:
Espresso.onView(ViewMatchers.withId(R.id.parent_recycler_list)).perform(
ViewActions.repeatedlyUntil(
ViewActions.swipeUp(),
Matchers.allOf(
ViewMatchers.hasDescendant(ViewMatchers.withText(text)),
isCompletelyDisplayed()
), 10
)
)
This will always swipe at least once... and can swipe past the view item I'm looking for.
Is there a way I can be more precise in when and how far to swipe?
I'm a bit of a novice still and don't know much about custom swipe actions on view holders. Thanks
When trying to use nestedScrollTo()
java.lang.RuntimeException: Action will not be performed because the
target view does not match one or more of the following constraints:
(view has effective visibility=VISIBLE and is descendant of a: (is
assignable from class: class androidx.core.widget.NestedScrollView))
You can simply use ViewActions.scrollTo() if your nested recycler views do not have nested scrolling enabled, but you'll need to tweak the action first because it does not support NestedScrollView:
fun nestedScrollTo(): ViewAction = object : ViewAction {
private val scrollTo = ViewActions.scrollTo()
override fun getConstraints(): Matcher<View> {
return Matchers.allOf(
ViewMatchers.withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE),
ViewMatchers.isDescendantOfA(Matchers.anyOf(ViewMatchers.isAssignableFrom(NestedScrollView::class.java))))
}
override fun getDescription(): String = scrollTo.description
override fun perform(uiController: UiController, view: View) = scrollTo.perform(uiController, view)
}
Then use the new custom action to scroll, for example:
onView(withText("query")).perform(nestedScrollTo(), click())
Avoid using swipe in this use case if possible, they can be unreliable at times.
6 months later I figured out a bit more about Recycler lists and that I was trying to use them wrong, or at least figured out which Recycler actions work (some don't seem to work at all). This had nothing to do with Nested scrolling even though I have nested recycler lists.
Needed to use a swipe up action as a back up for when the list doesn't exist in the hierarchy.
Also there is a Potential infinite loop.
fun tapRecyclerItem(titleText: String) {
val parentList by lazy { onView(withId(R.id.parent_recycler_list)) }
try {
//Try to scroll to the item in the child list
onView(allOf(
withId(R.id.recycler_list),
hasDescendantWithText(titleText)
)).perform(
RecyclerViewActions.actionOnItem<RecyclerView.ViewHolder>(
hasDescendantWithText(titleText),
ViewActions.scrollTo()
)
)
// Tap the title
onView(allOf(withId(R.id.title), withText(titleText))).tap()
} catch (ex: NoMatchingViewException) {
// Swipe up and try again
parentList.perform(swipeCenterUp())
tapRecyclerItem(titleText)
}
}
fun hasDescendantWithText(text: String): Matcher<View> {
return Matchers.allOf(
hasDescendant(withText(text)),
withEffectiveVisibility(VISIBLE)
)
}
fun swipeCenterUp(): ViewAction? {
return ViewActions.actionWithAssertions(
GeneralSwipeAction(
Swipe.FAST,
GeneralLocation.CENTER,
GeneralLocation.TOP_CENTER,
Press.FINGER
)
)
}