Lottie + Jetpack Compose - android

How can I use combination of Lottie json animation and Jetpack Compose UI in Android?
I already tried AndroidView(resId = R.layout.animation) where is com.airbnb.lottie.LottieAnimationView with field lottie_rawRes but is it the best way?

Lottie compose has been released:
Dependency:
implementation "com.airbnb.android:lottie-compose:$lottie_version"
Basic usage:
val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.my_animation))
LottieAnimation(composition)

As of compose version alpha05, Lottie seems to be working fine:
#Composable
fun CustomView(modifier: Modifier = Modifier
.fillMaxWidth()) {
val visibility = remember { mutableStateOf(0) }
val context = ContextAmbient.current
val customView = remember { LottieAnimationView(context) }
// Adds view to Compose
AndroidView({ customView }) { view ->
// View's been inflated - add logic here if necessary
with(view) {
setAnimation(R.raw.choose)
playAnimation()
repeatMode = LottieDrawable.INFINITE
foregroundGravity = Gravity.CENTER
}
}
}

First class support for Lottie has not been implemented yet but it will be eventually.

I'm not really sure if it is the best way to do it, but that's the current recommmended way to include an Android View in a #Composable function.
#Composable
fun <T : View> AndroidView(
viewBlock: (Context) -> T,
modifier: Modifier = Modifier,
update: (T) -> Unit = NoOpUpdate
)

It seems that they are working on it: https://github.com/airbnb/lottie-android/commits/gpeal/compose-1

Related

Infinite vertical carousel in Jetpack Compose

I'm looking to make an infinite vertical carousel in Jetpack Compose but I can't find how to do it.
I want something like this.
I did not find an example on the internet in Compose, everything is in XML.
I try https://google.github.io/accompanist/pager/ but it's not exactly what I want.
Thanks for helping me.
Solution 1:
It depends, you can use Pager Accompanist library:
https://github.com/google/accompanist/tree/main/pager
And you can check the docs here:
https://google.github.io/accompanist/pager/
Solution 2:
You can also try:
#Composable
fun CircularList(
items: List<String>,
modifier: Modifier = Modifier
) {
val listState = rememberLazyListState(Int.MAX_VALUE / 2)
LazyColumn(state = listState, modifier = modifier) {
items(
count = Int.MAX_VALUE,
itemContent = {
// your content
}
)
}
}
Good luck!

Do not clip bounds of AndroidView in Compose

From AndroidView documentation:
AndroidView will clip its content to the layout bounds, as being clipped is a common assumption made by Views - keeping clipping disabled might lead to unexpected drawing behavior. Note this deviates from Compose's practice of keeping clipping opt-in, disabled by default.
This seems to suggest that there is a way to turn clipping off, but I can't manage to do so.
I've tried:
Modifier.graphicsLayer(clip = false) on the AndroidView
clipToPadding = false on the View
clipToOutline = false on the View
clipChildren = false on the View
Is it possible to turn off clipping?
It's a known feature request, here's a workaround until it's implemented:
#Composable
fun <T : View> AndroidView(
clipToBounds: Boolean,
factory: (Context) -> T,
modifier: Modifier = Modifier,
update: (T) -> Unit = NoOpUpdate,
) {
androidx.compose.ui.viewinterop.AndroidView(
factory = factory,
modifier = modifier,
update = if (clipToBounds) {
update
} else {
{
(it.parent as? ViewGroup)?.clipChildren = false
update(it)
}
}
)
}

What's the equivalent of TextClock in Jetpack Compose?

TextClock is a widget for Android XML layouts which keeps and displays time on it's own. You just have to add the format and timezone.
Right now I don't see an equivalent in Jetpack Compose.
Should I implement it by my own with a Text composable and some time formatting libraries? Should I inflate a TextClock and make use of the backwards compatibility? Or is there a ready to use component?
I started with using the original TextView by adding it via the AndroidView composable. It does work but I would still appreciate an answer without "legacy" code.
#Composable
private fun TextClock(
modifier: Modifier = Modifier,
format12Hour: String? = null,
format24Hour: String? = null,
timeZone: String? = null,
) {
AndroidView(
factory = { context ->
TextClock(context).apply {
format12Hour?.let { this.format12Hour = it }
format24Hour?.let { this.format24Hour = it }
timeZone?.let { this.timeZone = it }
}
},
modifier = modifier
)
}
Here is my solution, which made styling possible.
#Composable
fun ClockText() {
val currentTimeMillis = remember {
mutableStateOf(System.currentTimeMillis())
}
LaunchedEffect(key1 = currentTimeMillis) {
while (true) {
delay(250)
currentTimeMillis.value = System.currentTimeMillis()
}
}
Box() {
Text(
text = DateUtils.formatDateTime(LocalContext.current, currentTimeMillis.value, DateUtils.FORMAT_SHOW_TIME),
modifier = Modifier.padding(8.dp, 8.dp),
color = MaterialTheme.colors.onBackground,
style = MaterialTheme.typography.subtitle2
)
}
}
https://gist.github.com/dino-su/c8edf1c206dd974b282326f3b9641ccc
TextClock class exteds TextView class and TextView class extends View class.
However Jetpack Compose is not based on 'Views'.
So I tried to find ways to use Views in Compose and I cound find this link.
https://developer.android.com/jetpack/compose/interop/interop-apis#views-in-compose
The link explains about AndroidView composable.
My sample code is as below and Merry Christmas!
#Composable
fun MyTextClock() {
AndroidView(factory = { context ->
TextClock(context).apply {
format12Hour?.let {
this.format12Hour = "a hh: mm: ss"
}
timeZone?.let { this.timeZone = it }
textSize.let { this.textSize = 24f }
}
})
}
#Preview(showBackground = true)
#Composable
fun MyTextClockPreview() {
MyTextClock()
}

How to keep list state across recompositions using NavGraph in Android's Jetpack Compose?

What should I do to make my composable remember the list state when navigating back to it?
If I understood correctly, when navigating "down" the composable is replaced by another one, so when going "up"/back it will create a new one and if I want something to persist in that situation I must hoist that value.
The thing is, I don't understand how to maintain a clean architecture if everything will be a property of my parent, in this case the activity which holds the NavHost and the composables for the NavGraph.
I've been looking to the Jetnews sample and they don't hoist anything to the NavGraph level, so how did they make the list to stay in the same position?
Activity
val navController = rememberNavController()
NavHost(
navController = navController,
startDestination = NavigationScreens.Launcher.route
) {
composable(NavigationScreens.Home.route) {
val viewModel = hiltViewModel<DealsViewModel>(backStackEntry = it)
HomeScreen(viewModel) { game ->
navController.putArgument(NavigationScreens.Pdp.Args.game, game)
navController.navigate(NavigationScreens.Pdp.route)
}
}
}
ViewModel
#HiltViewModel
class DealsViewModel #Inject constructor(
private val fetchGameDealsUseCase: FetchGameDealsUseCase,
private val dispatcherProvider: DispatcherProvider
) : ViewModel() {
private val scope = CoroutineScope(dispatcherProvider.io)
val deals = fetchGameDealsUseCase().cachedIn(scope)
}
HomeScreen
Scaffold(
scaffoldState = scaffoldState
) {
Box(modifier = Modifier.fillMaxSize()) {
SearchField()
DealsScreen(
deals = viewModel.deals,
contentPadding = PaddingValues(top = 84.dp),
onItemClick = onItemClick
)
}
}
DealsScreen
fun DealsScreen(
modifier: Modifier = Modifier,
contentPadding: PaddingValues = PaddingValues(0.dp),
deals: Flow<PagingData<Game>>,
onItemClick: (Game) -> Unit,
) {
val nColumns = 3
val content = deals.collectAsLazyPagingItems()
LazyVerticalGrid(
cells = GridCells.Fixed(nColumns),
modifier = Modifier
.fillMaxSize()
.composed { modifier },
contentPadding = contentPadding,
) {
items(content) { game ->
game?.let {
DealsContent(game, onItemClick)
}
}
}
}
The main difference I noted between my code and theirs is that they don't collect the data on the composable, but instead the use a producer which in this case I don't know what to do since I'm using the collectAsLazyPagingItems. Does anyone have a solution for this?
Full code is available at https://github.com/Danil0v3s/wishlisted-android/tree/compose
Edit: I've found that there's actually a bug which makes the composable defaults the list to initial position when the collectAsLazyPagingItems is used inside a NavHost

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