Unable to use TextClock in GlanceAppWidget as remote View - android

Based on the official documentation,
I can see TextClock seems to be supported now in new GlanceAPI as a Remote view, but I can not used in our example code,
class RemoteWidget : GlanceAppWidget(errorUiLayout = R.layout.widget_error_layout) {
override val stateDefinition: GlanceStateDefinition<*>
get() = CustomGlanceStateDefinition
#Composable
override fun Content() {
val pref = currentState<Preferences>()
Column(
modifier = GlanceModifier
.fillMaxSize()
.background(color = Color.Green)
.padding(8.dp)
) {
Text(
text = LocalContext.current.getString(R.string.app_name)
)
TextClock() //Error
AndroidView(factory = { TextClock(it) }) //Error
}
}
I don't know how to use TextClock in GlanceWidget

Glance has indeed interoperability with all available RemoteViews. You need to use the AndroidRemoteView composable part of Glance.
Note that you are using the AndroidView composable instead.
Here is an example:
val packageName = LocalContext.current.packageName
Column(modifier = GlanceModifier.fillMaxSize()) {
Text("My old button")
AndroidRemoteViews(RemoteViews(packageName, R.layout.my_awesome_old_button))
}

Related

Can I wrap Modifier with remember in Jetpack Compose?

In order to share settings among of compose functions, I create a class AboutState() and a compose fun rememberAboutState() to persist settings.
I don't know if I can wrap Modifier with remember in the solution.
The Code A can work well, but I don't know if it maybe cause problem when I wrap Modifier with remember, I think Modifier is special class and it's polymorphic based invoked.
Code A
#Composable
fun ScreenAbout(
aboutState: AboutState = rememberAboutState()
) {
Column() {
Hello(aboutState)
World(aboutState)
}
}
#Composable
fun Hello(
aboutState: AboutState
) {
Text("Hello",aboutState.modifier)
}
#Composable
fun World(
aboutState: AboutState
) {
Text("World",aboutState.modifier)
}
class AboutState(
val textStyle: TextStyle,
val modifier: Modifier=Modifier
) {
val rowSpace: Dp = 20.dp
}
#Composable
fun rememberAboutState(): AboutState {
val aboutState = AboutState(
textStyle = MaterialTheme.typography.body1.copy(
color=Color.Red
),
modifier=Modifier.padding(start = 80.dp)
)
return remember {
aboutState
}
}
There wouldn't be a problem passing a Modifier to a class. What you actually defined above, even if named State, is not class that acts as a State, it would me more appropriate name it as HelloStyle, HelloDefaults.style(), etc.
It would be more appropriate to name a class XState when it should have internal or public MutableState that can trigger recomposition or you can get current State of Composable or Modifier due to changes. It shouldn't contain only styling but state mechanism either to change or observe state of the Composble such as ScrollState or PagerState.
When you have a State wrapper object common way of having a stateful Modifier or Modifier with memory or Modifiers with Compose scope is using Modifier.composed{} and passing State to Modifier, not the other way around.
When do you need Modifier.composed { ... }?
fun Modifier.composedModifier(aboutState: AboutState) = composed(
factory = {
val color = remember { getRandomColor() }
aboutState.color = color
Modifier.background(aboutState.color)
}
)
In this example even if it's not practical getRandomColor is created once in recomposition and same color is used.
A zoom modifier i use for zooming in this library is as
fun Modifier.zoom(
key: Any? = Unit,
consume: Boolean = true,
clip: Boolean = true,
zoomState: ZoomState,
onGestureStart: ((ZoomData) -> Unit)? = null,
onGesture: ((ZoomData) -> Unit)? = null,
onGestureEnd: ((ZoomData) -> Unit)? = null
) = composed(
factory = {
val coroutineScope = rememberCoroutineScope()
// Current Zoom level
var zoomLevel by remember { mutableStateOf(ZoomLevel.Min) }
// Rest of the code
},
inspectorInfo = {
name = "zoom"
properties["key"] = key
properties["clip"] = clip
properties["consume"] = consume
properties["zoomState"] = zoomState
properties["onGestureStart"] = onGestureStart
properties["onGesture"] = onGesture
properties["onGestureEnd"] = onGestureEnd
}
)
Another practical example for this is Modifier.scroll that uses rememberCoroutineScope(), you can also remember object too to not intantiate another object in recomposition
#OptIn(ExperimentalFoundationApi::class)
private fun Modifier.scroll(
state: ScrollState,
reverseScrolling: Boolean,
flingBehavior: FlingBehavior?,
isScrollable: Boolean,
isVertical: Boolean
) = composed(
factory = {
val overscrollEffect = ScrollableDefaults.overscrollEffect()
val coroutineScope = rememberCoroutineScope()
// Rest of the code
},
inspectorInfo = debugInspectorInfo {
name = "scroll"
properties["state"] = state
properties["reverseScrolling"] = reverseScrolling
properties["flingBehavior"] = flingBehavior
properties["isScrollable"] = isScrollable
properties["isVertical"] = isVertical
}
)

Android Jetpack Compose Scroll to Top

In Jetpack Compose, where is ScrollToTopButton coming from? It is mentioned in Google's documentation. Annoyingly, they neglect to mention the package. I have imports of foundation version 1.2.0-alpha08; also tried with 1.2.0-beta02 as well as ui and material (1.1.1). Not found. (yes did do an internet search on the term, came back empty handed).
implementation "androidx.compose.foundation:foundation:${version}"
implementation "androidx.compose.foundation:foundation-layout:${version}"
implementation "androidx.compose.ui:ui:$compose_version"
implementation "androidx.compose.material:material:$compose_version"
#Composable
fun MessageList(messages: List<Message>) {
val listState = rememberLazyListState()
// Remember a CoroutineScope to be able to launch
val coroutineScope = rememberCoroutineScope()
LazyColumn(state = listState) {
// ...
}
ScrollToTopButton(
onClick = {
coroutineScope.launch {
// Animate scroll to the first item
listState.animateScrollToItem(index = 0)
}
}
)
}
Google documentation
Edit: If this is NOT a function they offer, but rather a suggestion to create your own, shame on whoever wrote the documentation, it literally suggests being a function offered by Compose.
Edit 2: Turns out it is a custom function (see the answer). What moved the author of the documentation to write it like this? Why not just put Button? Sigh.
It's not clear from the documentation but you actually have to make your own. For example you can use this:
#Composable
fun ScrollToTopButton(onClick: () -> Unit) {
Box(
Modifier
.fillMaxSize()
.padding(bottom = 50.dp), Alignment.BottomCenter
) {
Button(
onClick = { onClick() }, modifier = Modifier
.shadow(10.dp, shape = CircleShape)
.clip(shape = CircleShape)
.size(65.dp),
colors = ButtonDefaults.buttonColors(
backgroundColor = Color.White,
contentColor = Color.Green
)
) {
Icon(Icons.Filled.KeyboardArrowUp, "arrow up")
}
}
}
And then:
val showButton by remember{
derivedStateOf {
listState.firstVisibleItemIndex > 0
}
}
AnimatedVisibility(
visible = showButton,
enter = fadeIn(),
exit = fadeOut(),
) {
ScrollToTopButton(onClick = {
scope.launch {
listState.animateScrollToItem(0)
}
})
}

Android Jetpack Compose capable of creating List with interactive Buttons and Values on each item?

I'm currently thinking about creating an initiative tracker app for a friend of mine and since I've been out of touch with app development for quite a while I'm trying to evaluate which tools would best suit my needs.
Since the App is going to be developed for Android, I'm pretty sure I'll be using Kotlin and therefore Jetpack Compose caught my eye.
After making a bit of research and going through the docs though, I'm unsure if it is capable of what I want to achieve:
I want to be able to create a dynamic list of entry cards sorted by a certain value asigned to each card. (Which I'm relatively certain LazyColumns will be able to handle).
The catch is this: Each of these cards needs to have buttons and several additional values that can be manipulated with these buttons.
I haven't been able to find descriptions of something like this in the docs or any examples using Jetpack Compose LazyColumns.
I created a (badly made) mockup to hopefully better describe what it is I want to do:
Would anyone be able to share insights on if Jetpack Compose is capable of these features or if not could share advice on what tool to use instead?
Thanks alot and Kind regards.
Yes, with Compose, you can quite easily make such an application.
Here is a basic example of such a UI.
class MainActivity : FragmentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
AppTheme {
ItemsScreen()
}
}
}
}
class Item(val title: String, value1: Int, value2: Int, value3: Int) {
val value1 = mutableStateOf(value1)
val value2 = mutableStateOf(value2)
val value3 = mutableStateOf(value3)
val valueToSortBy: Int
get() = value1.value + value2.value + value3.value
}
class ScreenViewModel : ViewModel() {
val items = List(3) {
Item(
"Item $it",
Random.nextInt(10),
Random.nextInt(10),
Random.nextInt(10),
)
}.toMutableStateList()
fun sortItems() {
items.sortByDescending { it.valueToSortBy }
}
fun addNewItem() {
items.add(
Item(
"Item ${items.count()}",
Random.nextInt(10),
Random.nextInt(10),
Random.nextInt(10),
)
)
}
}
#Composable
fun ItemsScreen() {
val viewModel: ScreenViewModel = viewModel()
LaunchedEffect(viewModel.items.map { it.valueToSortBy }) {
viewModel.sortItems()
}
Box(
Modifier
.fillMaxSize()
.padding(10.dp)
) {
LazyColumn(
contentPadding = PaddingValues(vertical = 10.dp),
verticalArrangement = Arrangement.spacedBy(10.dp)
) {
items(viewModel.items) { item ->
ItemView(item)
}
}
FloatingActionButton(
onClick = viewModel::addNewItem,
modifier = Modifier.align(Alignment.BottomEnd)
) {
Text("+")
}
}
}
#Composable
fun ItemView(item: Item) {
Card(
elevation = 5.dp,
) {
Column(modifier = Modifier.padding(10.dp)) {
Row(
verticalAlignment = Alignment.CenterVertically,
) {
Text(
item.title,
style = MaterialTheme.typography.h5
)
Spacer(modifier = Modifier.weight(1f))
Text(
"Value to sort by: ${item.valueToSortBy}",
style = MaterialTheme.typography.body1,
fontStyle = FontStyle.Italic,
)
}
Spacer(modifier = Modifier.size(25.dp))
Row(
horizontalArrangement = Arrangement.SpaceBetween,
modifier = Modifier.fillMaxWidth()
) {
CounterView(value = item.value1.value, setValue = { item.value1.value = it })
CounterView(value = item.value2.value, setValue = { item.value2.value = it })
CounterView(value = item.value3.value, setValue = { item.value3.value = it })
}
}
}
}
#Composable
fun CounterView(value: Int, setValue: (Int) -> Unit) {
Row(verticalAlignment = Alignment.CenterVertically) {
CounterButton("+") {
setValue(value + 1)
}
Text(
value.toString(),
modifier = Modifier.padding(5.dp)
)
CounterButton("-") {
setValue(value - 1)
}
}
}
#Composable
fun CounterButton(text: String, onClick: () -> Unit) {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.size(45.dp)
.background(Color.LightGray)
.clickable(onClick = onClick)
) {
Text(
text,
color = Color.White,
)
}
}
I'm sorting by sum of 3 values here. Note that sorting items like this may not do much performant in a production app, you should use a database to store items and request sorted items from it.

How to disable ripple effect when clicking in Jetpack Compose

In Jetpack Compose, when you enable clickable {} on a modifier for a composable, by default it enables ripple effect for it. How to disable this behavior?
Example code
Row(modifier = Modifier
.clickable { // action }
)
Short answer:
to disable the ripple pass null in the indication parameter in the clickable modifier:
val interactionSource = remember { MutableInteractionSource() }
Column {
Text(
text = "Click me without any ripple!",
modifier = Modifier
.clickable(
interactionSource = interactionSource,
indication = null
) {
/* doSomething() */
}
)
Why it doesn't work with some Composables as Buttons:
Note that in some Composables, like Button or IconButton, it doesn't work since the indication is defined internally by the component which uses indication = rememberRipple(). This creates and remembers a Ripple using values provided by RippleTheme.
In this cases you can't disable it but you can change the appearance of the ripple that is based on a RippleTheme. You can define a custom RippleTheme and apply it to your composable with the LocalRippleTheme.
Something like:
CompositionLocalProvider(LocalRippleTheme provides NoRippleTheme) {
Button(
onClick = { /*...*/ },
) {
//...
}
}
with:
private object NoRippleTheme : RippleTheme {
#Composable
override fun defaultColor() = Color.Unspecified
#Composable
override fun rippleAlpha(): RippleAlpha = RippleAlpha(0.0f,0.0f,0.0f,0.0f)
}
Custom modifier
If you prefer you can build your custom Modifier with the same code above, you can use:
fun Modifier.clickableWithoutRipple(
interactionSource: MutableInteractionSource,
onClick: () -> Unit
) = composed(
factory = {
this.then(
Modifier.clickable(
interactionSource = interactionSource,
indication = null,
onClick = { onClick() }
)
)
}
)
and then just apply it:
Row(
modifier = Modifier
.clickableWithoutRipple(
interactionSource = interactionSource,
onClick = { doSomething() }
)
){
//Row content
}
Long answer:
If you add the clickable modifier to a composable to make it clickable within its bounds it will show an Indication as specified in indication parameter.
By default, indication from LocalIndication will be used.
If you are using a MaterialTheme in your hierarchy, a Ripple, defined by rememberRipple(), will be used as the default Indication inside components such as androidx.compose.foundation.clickable and androidx.compose.foundation.indication.
Use this Modifier extension:
fun Modifier.noRippleClickable(onClick: () -> Unit): Modifier = composed {
clickable(indication = null,
interactionSource = remember { MutableInteractionSource() }) {
onClick()
}
}
then simply replace Modifier.clickable {} with Modifier.noRippleClickable {}
Row(modifier = Modifier.noRippleClickable {
// action
})
To disable the ripple effect, have to pass null to indication property of the modifier.
More about indication on Jetpack Compose documentation
Code
Row(
modifier = Modifier
.clickable(
indication = null,
interactionSource = remember { MutableInteractionSource() } // This is mandatory
) {
// action
}
)
You can handle it this way when working with Buttons.
Create a Ripple interactionSource class
class NoRippleInteractionSource : MutableInteractionSource {
override val interactions: Flow<Interaction> = emptyFlow()
override suspend fun emit(interaction: Interaction) {}
override fun tryEmit(interaction: Interaction) = true
}
In case of a button, you can handle it by passing the ripple interaction class as the interactionSource parameter i.e:
Button(
onClick = { /*...*/ },
interactionSource = NoRippleInteractionSource()
) {
//..
}
This solution works with all compossables that accept a mutableInteractionSource as a parameter for example Button(), TextButton(), Switch(), etc
Modifier extension with other parameters :
inline fun Modifier.noRippleClickable(
enabled: Boolean = true,
onClickLabel: String? = null,
role: Role? = null,
crossinline onClick: ()->Unit
): Modifier = composed {
clickable(
enabled = enabled,
indication = null,
onClickLabel = onClickLabel,
role = role,
interactionSource = remember { MutableInteractionSource() }) {
onClick()
}
}
With androidx.compose.foundation there is a enabled attribute inside clickable extension. I think that it is easiest way. Link
fun Modifier.clickable(
interactionSource: MutableInteractionSource,
indication: Indication?,
enabled: Boolean = true,
onClickLabel: String? = null,
role: Role? = null,
onClick: () -> Unit
): Modifier
I used #Mahdi-Malv's answer and modify as below:
remove inline and crossinline
modify according to my requirement
fun Modifier.noRippleClickable(
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
indication: Indication? = null,
enabled: Boolean = true,
onClickLabel: String? = null,
role: Role? = null,
onClick: () -> Unit,
) = clickable(
interactionSource = interactionSource,
indication = indication,
enabled = enabled,
onClickLabel = onClickLabel,
role = role,
onClick = onClick,
)

Using Custom Views with Jetpack Compose

Let's suppose I'm using some library that's intended to provide some UI Widgets.
Let's say this library provides a Button Widget called FancyButton.
In the other hand, I have a new project created with Android Studio 4 that allows me to create a new project with an Empty Compose Activity.
The question is:
How should I add this FancyButton to the view stack? Is it possible? Or with Jetpack Compose I can only use components that had been developed specifically for Jetpack Compose. In this case, AFAIK I could only use Android standars components (Text, MaterialTheme, etc).
If I try to use something like this:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MaterialTheme {
Greeting("Android")
FancyButton(context, "Some text")
}
}
}
then I get this error:
e: Supertypes of the following classes cannot be resolved. Please make sure you have the required dependencies in the classpath.
Currently (as of 0.1.0-dev04), there is not a good solution to this. In the future, you'll be able to simply call it as FancyButton("Some text") (no context needed).
You can see a demo of what it will look like in the Compose code here.
Update in alpha 06
It is possible to import Android View instances in a composable.
Use ContextAmbient.current as the context parameter for the View.
Column(modifier = Modifier.padding(16.dp)) {
// CustomView using Object
MyCustomView(context = ContextAmbient.current)
// If the state updates
AndroidView(viewBlock = ::CustomView, modifier = modifier) { customView ->
// Modify the custom view
}
// Using xml resource
AndroidView(resId = R.layout.view_demo)
}
You can wrap your custom view within the AndroidView composable:
#Composable
fun RegularTextView() {
AndroidView(
factory = { context ->
TextView(context).apply {
text = "RegularTextView"
textSize = 34.dp.value
}
},
)
}
And here is how to update your custom view during a recomposition, by using the update parameter:
#Composable
fun RegularTextView() {
var string by remember {
mutableStateOf("RegularTextView")
}
Column(horizontalAlignment = Alignment.CenterHorizontally) {
AndroidView(
factory = { context ->
TextView(context).apply {
textSize = 34.dp.value
}
},
update = { textView ->
textView.text = string
}
)
Spacer(modifier = Modifier.height(8.dp))
Button(
onClick = {
string = "Button clicked"
},
) {
Text(text = "Update text")
}
}
}
#Composable
fun ButtonType1(text: String, onClick: () -> Unit)
{
Button (
modifier=Modifier.fillMaxWidth().height(50.dp),
onClick = onClick,
shape = RoundedCornerShape(5.dp),
border = BorderStroke(3.dp, colorResource(id = R.color.colorPrimaryDark)),
colors = ButtonDefaults.buttonColors(contentColor = Color.White, backgroundColor = colorResource(id = R.color.colorPrimaryDark))
)
{
Text(text = text , color = colorResource(id = R.color.white),
fontFamily = montserrat,
fontWeight = FontWeight.Normal,
fontSize = 15.sp
)
}
}

Categories

Resources