As per https://developer.android.com/jetpack/compose/interop/compose-in-existing-ui#compose-recyclerview, the composable ViewHolder that can be used in RecyclerView is as below
import androidx.compose.ui.platform.ViewCompositionStrategy
class MyComposeViewHolder(
val composeView: ComposeView
) : RecyclerView.ViewHolder(composeView) {
init {
composeView.setViewCompositionStrategy(
ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed
)
}
fun bind(input: String) {
composeView.setContent {
MdcTheme {
Text(input)
}
}
}
}
It looks like, every time it is bind the composable setContent (i.e. redraw again).
I measure the speed using Profile GPU Rendering, it clearly show that the Hybrid of ReyclerView+ComposeViewHolder is slower than pure RecyclerView or LazyColumn.
You can get the design here
How can we speed up the Hybrid RecyclerView+ComposeViewHolder?
The latest version of RecyclerView (1.3.0-alpha02) and Compose (1.2.0-beta02) have improved support and performance for Compose when used in RecyclerView thanks to the PoolingContainer library it uses. No need to dispose nor set the ViewCompositionStrategy. Note that the content in https://developer.android.com/jetpack/compose/interop/compose-in-existing-ui#compose-recyclerview is only needed if you are using stable versions of the libraries.
Please run release build of the application with R8 enabled. This increases performance significantly.
Try using a diff view composition strategy, one that is tied to the lifecycle of the fragment/activity. That way your composable isn't based on attach/detatch of the view which happens a lot
Related
When using Jetpack Compose navigation, would one use one ViewModel for all screens or one ViewModel for each screen? Is there any architecture guidance on this? And if one would use multiple ViewModels, where would one instantiate them?
I'm no professional when it comes to architectural design in software but I think that it all depends on your app's structure. Compose is basically a UI framework, and it's not a replacement for ViewModels. This means that you still have all the flexibility to go about it any way you see fit.
For example, if you have multiple different screens (i.e. Login, Home, Settings, etc.), you're probably better off using a separate ViewModel for each screen. If you have a very simple one-pager, you could get away with a single ViewModel.
The official docs have a pretty good description on how to go about structuring your app. You should obviously make any necessary adjustments in order to make the architecture fit your projects needs & goals.
As for the instantiation part, you can either follow the suggested (no D.I.) way, i.e.:
class ExampleViewModel : ViewModel() { /*...*/ }
#Composable
fun MyExample(
viewModel: ExampleViewModel = viewModel()
) {
// use viewModel here
}
or you could (and probably should) use a D.I. library such as Hilt or Koin.
I know that in Jetpack Compose you have to change the state of the passed in data in order to trigger a recomposition of the UI to update the UI with any changes. I have also read the documentation about Jetpack Compose state and ViewModels here. But that's a very simple example and does not cover the use case below.
Below is a conceptual scenario where I want to update the state of the list, by updating just one item's state that I wish to be reflected in the Jetpack Compose rendered part. I know I must assign a new list as data, which should trigger the recomposition and below I am using toMutableList() to try to achieve this. But this does not work. When I run this kind of code, recomposition does not happen and the single item's state is not updated in the list.
Could someone please explain to me why this does not work and how should I approach this?
I already know of mutableStateListOf(), but how should I approach this if I want to keep my view models compatible with other non-Jetpack Compose parts of my app, and thus I only want to use LiveData in my view models?
class Model : ViewModel() {
private val _items = MutableLiveData(listOf<Something>())
val items: LiveData<String> = _items
fun update(item: Something) {
_items.value = _items.value!!.toMutableList().map {
if (it == item) {
// Update item. But it's not reflected in Jetpack Compose
}
}
}
}
#Composable
fun ListComponent(model: Model) {
val items by model.items.observeAsState(emptyList())
LazyColumn {
items(items) { item ->
...
}
}
}
I think it's because you are mutating array instead of copying it. Compose needs stable equality when talking about recomposition avoidance, here i believe it can only use reference. Try copying array and then mutating the new one. I believe if you do map without toMutableList() it will create a copy and do exactly what you want
I am learning Jetpack compose, and I have been seen so far that lifting the state up to a composable's caller to make a composable stateless is the way to go. I`ve been using this pattern in my Compose apps.
For an app state that I need to modify from many different places of the tree, I will have to pass around a lot of callbacks, This can become difficult to manage.
I have some previous experience with Flutter. The way Flutter deals with providing a state to its descendants in the tree to overcome the above, is to use other mechanisms to manage state, namely Provider + ChangeNotifier.
Basically, with Provider, a Provider Widget is placed in the tree and all the children of the provider will have access to the values exposed by it.
Are there any other mechanisms to manage state in Jetpack Compose apps, besides State hoisting? And, what would you recommend?
If you need to share some data between views you can use view models.
#Composable
fun TestScreen() {
val viewModel = viewModel<SomeViewModel>()
Column {
Text("TestScreen text: ${viewModel.state}")
OtherView()
}
}
#Composable
fun OtherView() {
val viewModel = viewModel<SomeViewModel>()
Text("OtherScreen text: ${viewModel.state}")
}
class SomeViewModel: ViewModel() {
var state by mutableStateOf(UUID.randomUUID().toString())
}
The hierarchy topmost viewModel call creates a view model - in my case inside TestScreen. All children that call viewModel of the same class will get the same object. The exception to this is different destinations of Compose Navigation, see how to handle this case in this answer.
You can update a mutable state property of view model, and it will be reflected on all views using that model. Check out more about state in Compose.
The view model lifecycle is bound to the compose navigation route (if there is one) or to Activity / Fragment otherwise, depending on where setContent was called from.
I deal with loads of architecture design patterns and guidelines to choose from and to follow as an android client-server app developer.
The most popular ones are:
MVP or MVVM
Clean architecture
Repository pattern
Dependency injection technique
and so on...
Due to the strict rules of patterns, a developer has to admit the fact that patterns are just recommendations themselves, and are not required by Android SDK at all.
Same is true for LibGDX. There are no strict rules or requirements provided by LibGDX library, so the developer is free to decide how to write the game.
So the question is:
Are there some recommendations, design guidelines or even standards for LibGDX game developers to follow? How should I write the code (with usage of LibGDX) in a way that other developer can easily understand?
From my experiences, there is no standard everyone is following. libGDX developers come from different backgrounds. Some are backend developers in their day life, some are just hobbyist devs and learn their first development skills.
I see a lot of libGDX open-sourced projects with typical static SomeManager.getInstance() calls, while I prefer to pass-through references (as a backend developer, you will know about the advantages - testability and so on).
Even the libGDX backend itself does not follow one single approach. There are some parts getting references to managers by reflection (which is not good, because you must exclude such classes from obfusciation) and some using static getInstances().
If you also HTML5, you also must respect some GWT-based restrictions, so you are sometimes forced to go a way you would never do when developing Spring Boot applications.
Now that 3 years passed I can answer this question myself.
I found that using ECS (Entity-Component-System) is the best approach for creating games.
With this approach you'll have 3 different purpose objects. As name suggests they are:
Entity - is just a general purpose object which only contains set of component objects, and usually an ID.
Component - is a bag of data. No logic just plain POJO. The data it contains will define the behaviour of Entity which contains the component.
System - is where you put your logic. Every system should process an entity if and only if it contains a very specific set of components.
Engine - is a representation of a scene where all entities live, and are processed by systems.
The easies way of implementing such approach is by using Ashley lib. It's a lightweight implementation of ECS. Although as my experience has shown this works best in a case of real gameplay. If you want to have very specific animations (even of game objects) then use scene2d.
Scene2d represents a more familiar (to vast majority of developers) approach. It's a graph of UI elements which are placed in a hierarchy. There are very useful Actions which can be used to implement your animations. It also works best for any UI that you want to display in your game.
So to sum it all up I have worked out that you should have a couple of classes per screen to have a distinct division of labour.
Screen - I see it as a branch of running code. It contains an Engine (if we need an ECS) and a Stage (if we need an UI). Handles all game events, user input and routing.
Engine - ecs container which knows how to manage its systems, entities and components.
Stage - UI container which knows what UI elements should be present, how they should look and behave.
Simple example in kotlin (NOTE in my real project I use koin DI to bind all together):
class MyGameScreen : Screen, MyGameEngine.Callback, MyGameStage.Callback {
val engine = MyGameEngine(callback = this)
val stage = MyGameStage(callback = this)
override fun create() {
engine.create()
stage.create()
}
override fun render(delta: Float) {
enigne.update(delta)
stage.act(delta)
stage.draw()
}
override fun dispose() {
enigne.dispose()
stage.dispose()
}
override fun buttonPress() {
//handle button press
}
override fun onGameEvent(event: MyGameEngine.Event) {
//handle some game event
}
}
class MyGameEngine(
private val callback: Callback
) : com.badlogic.ashley.core.PooledEngine() {
fun create() {
// create and add all your systems and entities
}
sealed class Event {
// ...
}
interface Callback {
fun onGameEvent(event: Event)
}
}
class MyGameStage(
private val callback: Callback
) : com.badlogic.gdx.scenes.scene2d.Stage() {
val button1 = TextButton(...).apply {
addClickListener { callback.buttonPress() }
}
val label1 = Label(...)
// and so on
fun create() {
val root = Table().apply {
// add all your actors to the root table
}
val rootContainer = Container(root).apply {
setFillParent(true)
fill()
top()
}
addActor(rootContainer)
}
fun startSomeAnimation() {
val action = Actions.sequence(
// your animation
)
addAction(action)
}
interface Callback {
fun buttonPress()
}
}
I need to map domain objects to UI objects and display using a live paged list.
I have tried to map LiveData<PagedList<X>> to LiveData<PagedList<Y>>, and map PositionalDataSource<X> to PositionalDataSource<Y>, but due to package private and private restrictions these both appear to be impossible without placing my code in android.arch.paging package and using reflection, or using a modified version of the paging lib.
Does anyone know of a way to do this without resorting to such undesirable methods?
(Note that this would not be an issue if the paging library API used interfaces instead of abstract base classes - that would allow wrapping any paged list/data source and add appropriate mappings.)
DataSource and DataSource.Factory have mapBy() and mapPageBy(), which could help you in this case. Just be careful cause these two will restrict the size of the "Y"-result-list.
If the size of the result differs from the original list's size then DataSource will throw an Exception.
For me following worked :
val dataSourceFactory =
cache.getDataSourceFactory(params)
.map {
convertXToY(it)
}
Paging 3 library PagingData mapping (RxPagingSource+RxJava2)
val pagingData: PagingData<X> = //TODO
pagingData.map { pagingData ->
pagingData.mapAsync { x ->
Single.just(Y(x))
}
}