stringResource() causing recompose of composition - kotlin

I am beginner at jetpack compose. I was debugging recomposition but suddenly I saw a unusual recomposition in Header compose function when app start.
I find out the reason or culprit for the recomposition that I used in Header compose function to get string text by stringResource().. If I use context.getString() or hardcode string value instead of stringResource() then I got no recomposition.
This code when showing the recomposition
#Composable
fun MainScreen() {
Header()
}
#Composable
fun Header() {
Text(
text = stringResource(id = R.string.app_name)
)
}
But If I use these codes No more recomposition. But why?
#Composable
fun MainScreen() {
Header()
}
#Composable
fun Header() {
val context = LocalContext.current
Text(
text = context.getString(R.string.app_name)
)
}
So what can I do for get rid of recomposition when using stringResource() into compose functions

if you have the value saved in the res/values/strings.xml file then using compose the only thing you will need to do is calling the stringResource(R.string.app_name). Jetpack Compose handles getting the resource on its own. you wont even need to get it from your Context.
see here for docs on resource.
that should not cause recomposing every time but if it does it is always a good practice to save your values inside of a remember so that it knows not to recompose every time. the problem might be from a different part of your code.

First of all, this behavior shouldn't be happening, I recommend creating a clean project and trying again.
But... to avoid recomposing inside Composable, the Effect API would be useful:
val context = LocalContext.current
var appName = ""
LaunchedEffect(Unit) {
appName = context.getString(R.string.app_name)
}
Text(
text = appName
)
The codes inside the LaunchedEffect block are only executed once, even if the recomposition happens.
Documentaion of api Side Effects

Related

Collecting Flow<List> and displaying it in Compose (Kotlin)

Hello guys I have list of movies that I call from MovieApi.
In movieRepo I did this:
override suspend fun getPopularMovies() : Flow<List<Movie>>{
val popularMovies : Flow<List<Movie>> = flow{
while(true){
val lastMovie = movieApi.getPopularMovies()
Log.i("EMIT", "${emit(lastMovie)}")
kotlinx.coroutines.delay(5000)
}
}
return popularMovies
}
In MovieViewModel:
init{
viewModelScope.launch {
repository.getPopularMovies().collect(){
Log.i("COLLECTED", "$it")
}
}
}
private suspend fun getPopularMovies() {
return repository.getPopularMovies().collect()
}
I know that collect gets all Movies I want, but I need to display it in my HomeScreen with viewModel when I call getPopularMovies.
I'm reading Flow docs but cant understan how this part works(news part is from Flow documentation):
newsRepository.favoriteLatestNews.collect { favoriteNews ->
// Update View with the latest favorite news
}
I have the same question too actually. Curious to see if you had found out anything.
I could be mistaken in this but I would like to gain a better understanding in this so I would appreciate for other to chime in as well.
Assuming you're using targeting a recyclerview.
For non-Viewmodel collection approach, the collection has to be done in the UI layer.
In collect block, you will need to pass movie list to adapter's submitList.
But if you still want to do collection in ViewModel, you will need to create a UIState as a StateFlow. Collect the movie list into a UI state.
In UI layer, collect the UI state and access the movie list from it

How to nicely init state from database in Jetpack Compose?

I have a Jetpack Compose (desktop) app with a database, and I want to show some UI based on data from the db:
val data = remember { mutableStateListOf<Dto>() }
Column {
data.forEach { /* make UI */ }
}
My question is, at which point should I execute my database query to fill the list?
I could do
val data = remember { mutableStateListOf<Dto>() }
if (data.isEmpty()) data.addAll(database.queryDtos())
The isEmpty check is needed to prevent requerying on re-compose, so this is obviously not the way to go.
Another option would be
val data = remember {
val state = mutableStateListOf<Dto>()
state.addAll(database.queryDtos())
state
}
This way I can't reuse a database connection, since it's scoped inside the remember block. And queries should probably happen async, not inside this initializer
So, how to do this nicely?
In Android the cleanest way is using view model, and call such code in init.
In Desktop it depends on the operation. The main benefit of this platform is that there's no such thing as Android configuration change, so remember/LaunchedEffect are not gonna be re-created.
If the initialization code is not heavy, you can run it right inside remember.
val data = remember { database.queryDtos() }
In case you need to update the list later, add .toMutableStateList()
If it's something heavy, it's better to go for LaunchedEffect. It will have the same lifecycle as remember - run the containing code only the first time the view appears:
val data = remember { mutableStateListOf<Dto>() }
LaunchedEffect(Unit) {
data.addAll(database.queryDtos())
}

Why doesn't App crash when I use collect a flow from the UI directly from launch in Jetpack Compose?

I have read the article. I know the following content just like Image B.
Warning: Never collect a flow from the UI directly from launch or the launchIn extension function if the UI needs to be updated. These functions process events even when the view is not visible. This behavior can lead to app crashes. To avoid that, use the repeatOnLifecycle API as shown above.
But the Code A can work well without wrapped with repeatOnLifecycle, why?
Code A
#Composable
fun Greeting(handleMeter: HandleMeter,lifecycleScope: LifecycleCoroutineScope) {
Column(
modifier = Modifier.fillMaxSize()
) {
var my by remember { mutableStateOf(5)}
Text(text = "OK ${my}")
var dataInfo = remember { handleMeter.uiState }
lifecycleScope.launch {
dataInfo.collect { my=dataInfo.value }
}
}
class HandleMeter: ViewModel() {
val uiState = MutableStateFlow<Int>(0)
...
}
Image B
Code A will not work in real life. If you need to run some non-UI code in a composable function, use callbacks (like onClick) or LaunchedEffect (or other side effects).
LaunchedEffect {
dataInfo.collect {my=dataInfo.value}
}
Side effects are bound to composables, there is no need to specify the owner of their lifecycle directly.
Also, you can easily convert any flow to state:
val my = handleMeter.uiState.collectAsState()

How to i chain two parts of an animation together in jetpack compose so the the offset increases then decreases?

Ive recently got into doing animations using jet pack compose and am wondering how you can make it so that when you increase a value in an offset, once the animation reaches that value it then changes the value to another value. So like update transition but instead of at the same time, one after the other.
Actually #RaBaKa's answer is partially correct, but it's missing information about how the animation should be run.
It should be done as a side effect. For example, you can use LaunchedEffect: it is already running in a coroutine scope. It is perfectly normal to run one animation after another - as soon as the first suspend function finishes, the second will be started:
val value = remember { Animatable(0f) }
LaunchedEffect(Unit) {
value.animateTo(
20f,
animationSpec = tween(2000),
)
value.animateTo(
10f,
animationSpec = tween(2000),
)
}
Text(value.value.toString())
If you want to do this in response to some action, such as pressing a button, you need to run the coroutine yourself. The main thing is to run the animations in the same coroutine so that they are chained.
val value = remember { Animatable(0f) }
val scope = rememberCoroutineScope()
Button(onClick = {
scope.launch {
value.animateTo(
20f,
animationSpec = tween(2000),
)
value.animateTo(
10f,
animationSpec = tween(2000),
)
}
}) {
}
Text(value.value.toString())
The correct answer is to use Kotlin coroutines. I managed to get it working fine. You have to use coroutines in order to launch the animations in the correct sequence like this:
animationRoutine.launch {
coroutineScope {
launch {
animate(
startingValue,
targetValue,
animationSpec = whatYouWant,
block = { value, _ -> whateverYouNeed = value }
)
}
launch {
animate(
initialValue,
targetValue,
animationSpec = whatYouWant,
block = { value, _ -> whateverYouNeed = value }
)
}
}
Each of launch scope launches everything in a non blocking way if you tell it to allowing you to run multiple animations at once at a lower level and to sequence the animations you add another coroutine for the next part of the animation.
Maybe you can use Animatable
val value = remember { Animatable(0f) } //Initial Value
Then in compose you can just use
value.animateTo(20f)
then
value.animateTo(10f)
For more information visit the official documentation

Do I need use remember only for a title in Scaffold?

I'm learning Compose, the following code is from the article.
The author use var toolbarTitle by remember { mutableStateOf("Home") } only for a title, is it necessary ?
I think var toolbarTitle= mutableStateOf("Home") is enough, right?
Source Code
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
JetpackComposeScaffoldLayoutTheme {
// A surface container using the 'background' color from the theme
Surface(color = MaterialTheme.colors.background) {
var toolbarTitle by remember {
mutableStateOf("Home")
}
val scaffoldState =
rememberScaffoldState(rememberDrawerState(initialValue = DrawerValue.Closed))
val scope = rememberCoroutineScope()
Scaffold(
modifier = Modifier.background(Color.White),
scaffoldState = scaffoldState,
topBar = {
AppToolbar(
scaffoldState = scaffoldState,
scope = scope,
toolbarTitle = toolbarTitle
)
}, drawerContent = {
DrawerContent(scaffoldState = scaffoldState, scope = scope)
},
...
)
}
}
}
}
}
If you don't use remember the value will reset again to Home on every recomposition by using remember the value will be persisted even after a recomposition
by the way recomposition means when the composable renders again which can happen a lot of times when something changes on the screen and needs to be rendered again
I think, if the article is from a reputed source, the variable might have some further usage in the project. The very reason for initialising the variable as a MutableStateis that the developer wants recompositions to occur upon the change of this value.
If this was not the case, it could have been just var title = "Home", or better instead just use "Home" in the parameter, no need of a variable at all. You see, if you are creating a MutableState, in most scenarios, it is useless to declare it without using remember. In fact, the only scenario I can think of, to declare a MutableState without remeber is to trigger recompositions manually using the var as a trigger.
Anyway, most of the times, you want to read the value of the var that is declared MutableState. If any modifications are made to the value of the var, then a recomposition is triggered. Now, if you declare it without any rememberance, the value will be re-initlaised to whatever you provided as the initial value. The updated value is gone for good in this case.
Hence, in the latest versions of the compiler, I think it will not even allow you to create a MutableState var without using remember. If not a compile-time error, I'm sure it gives at least a warning (though I am almost certain it won't allow you to compile, which makes me think Compose Developers do not want us to trigger dummy recompositions!)
PS: The recompositions can be triggered manually by using remember too, so I guess that was not their motto.
If you want to change toolbarTitle later in your code you would have to use remember { mutableStateOf("Home") }
If it is always supposed to be "Home" you can just use val toolbarTitle = "Home" or use "Home" directly in AppToolbar()