Why is setContent{} being called twice? - kotlin

I've recently started my first project using Jetpack Compose (with minimal Android dev experience).
For checking performance, I logged each call to any function / composable, with some unexpected behavior at startup or orientation change (but without further interacting with the app):
I do understand oncreate / super being called (again, in case of orientation change), but why is it that setContent {} is being called twice?
override fun onCreate(savedInstanceState: Bundle?) {
Log.v(tag, "oncreate")
super.onCreate(savedInstanceState)
Log.v(tag, "oncreatesuper")
setContent {
Log.v(tag, "setting content")
Content()
}
}
and then
#Composable
private fun Content() {
val arrayOfNodes = rememberSaveable { mutableListOf<Wurzel>() }
val toggleSizeInputDialog = rememberSaveable { mutableStateOf(false) }
(...some more...)
val currentConfig = LocalConfiguration.current
val title = stringResource(id = R.string.title)
Log.v(tag, "recomposing content")
MyTheme {
when (currentConfig.orientation) {
Configuration.ORIENTATION_LANDSCAPE -> {
Scaffold(...........)
My project is far to small to see any perfomance issues, however I'd like to find out the reason for this behavior for future reference or whether I severely misunderstood the compose architecture.

setComponent() is not being called twice, but the composable function you pass in it is. It just seems like some event causes recomposition of the content composable.

Related

Jetpack Compose not updating / recomposing Flow List Values from Room DB when DB Data is getting changed

I'm trying to show a List of Items in my Android App. I'm using Jetpack Compose, Flows and RoomDB.
When launching the Activity all Items are shown without any problems, the Flow get's items collected and they are displayed.
But when I change some properties of the Item in the Database, the changes are not displayed. In my case I change the item to deleted, but it's still displayed as not deleted.
When I look at the Database Inspector, the value is changed in the database and set to deleted.
When I log collecting the flow, the change is getting emitted (It says the Item is set to deleted)
But Jetpack Compose is not recomposing the change.
If I delete an element from / add an element to the List (in the DB) the UI gets updated and recomposed.
So I can only assume that the problem must lie in the recomposition or handling of the flow.
Here my Code:
My Activity:
#AndroidEntryPoint
class StockTakingHistoryActivity : ComponentActivity() {
private val viewModel: StockTakingHistoryViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel.stockList = ...
setContent {
LaunchedEffect(Unit) {
viewModel.getStockListItems(viewModel.stockList!!.uuid)
}
Surface(color = MaterialTheme.colors.background) {
Content(viewModel.stockListItems)
}
}
}
}
...
#Composable
private fun Content(items: List<StockListItem>) {
...
LazyColumn {
items(items) { item ->
HistoryItem(stockListItem = item)
}
}
}
}
...
#Composable
private fun HistoryItem(stockListItem: StockListItem) {
...
Text(text = stockListItem.deleted)
...
Button(onClick = {
viewModel.deleteItem(stockListItem)
}) {
Text(text = "Set to deleted!")
}
}
}
My ViewModel:
var stockListItems by mutableStateOf(emptyList<StockListItem>())
fun getStockListItems(uuid: String) {
viewModelScope.launch {
stockListItemRepository.findByUUID(uuid).collect { items ->
Log.d("StockTakingHistoryViewModel", "items changed! ${items.map { it.deleted }}")
stockListItems = items
}
}
}
fun deleteItem(stockListItem: StockListItem) {
viewModelScope.launch(Dispatchers.IO) {
stockListItemRepo.update(item.copy(deleted = true);
}
}
The Repository:
fun findByUUID(uuid: String): Flow<List<StockListItem>> {
return dao.findByUUID(uuid)
}
The Dao behind the Repository Request:
#Query("select * from StockListItem where stockListUUID = :uuid order by createdAt desc limit 30")
fun findByUUID(uuid: String): Flow<List<StockListItem>>
I would be very happy if someone could help me! Thank you!
Considering you can collect a flow as state (via collectAsState) I'd consider going that route for getting the list rather than calling collect in the viewModel and updating the stockListItems as there are fewer moving parts for things to go wrong.
For example something like the following:
setContent {
val stockListItems = viewModel.getStockListItemsFlow(uuid).collectAsState(initial = emptyList())
Surface(color = MaterialTheme.colors.background) {
Content(stockListItems)
}
}
Found the Problem: The equals() method of StockListItem only compared the primary key.

how do i update configuration in kotlin for change language on radio btn selected in kotlin

how to update this one without using resources.updateConfiguration this commented codes please any can say? and solve it.
when i click th eradio btn an dthe language will have to change
this is mainActivity.kt file
class LanguageChange : AppCompatActivity() {
#RequiresApi(Build.VERSION_CODES.N)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_language_change)
radio_group_btn.setOnCheckedChangeListener { _, i ->
when(i){
R.id.radio_btn_fr_id -> {
setLang("fr")
txt_view1_id.setText(R.string.view_one)
txt_view2_id.setText(R.string.view_two)
Log.i("tag","french")
}
R.id.radio_btn_en_id -> {
setLang("en")
txt_view1_id.setText(R.string.view_one)
txt_view2_id.setText(R.string.view_two)
Log.i("tag","english")
}
}
}
}
private fun Context.setLang(lan: String): Context{
// val config = resources.configuration
// config.setLocale(Locale(lan))
// resources.updateConfiguration(config,resources.displayMetrics)
// onConfigurationChanged(config)
val config = resources.configuration
config.setLocale(Locale(lan))
config.setLayoutDirection(Locale(lan))
applyOverrideConfiguration(config)
return createConfigurationContext(config)
}
#RequiresApi(Build.VERSION_CODES.N)
override fun attachBaseContext(newBase: Context?) {
super.attachBaseContext(ContextWrapper(newBase?.setLang("en")))
}
}
It's a good question. Short answer you can't. Even with updateConfiguration you can't be sure about result. For more details please read this article https://medium.com/swlh/android-app-specific-language-change-programmatically-using-kotlin-d650a5392220. But i say it again there is no 100% method to change language inside app with default strings.

Why is data being shown when screen rotates in jetpack compose

I'm facing this issue where the data I'm retrieving from an API, https://randomuser.me/api/ at first compose it doesn't load.
But every time I rotate the screen the data updates.
First load
After screen rotation
View
class MainActivity : ComponentActivity() {
private val userViewModel : UserViewModel by viewModels()
private var userList: List<UserModel> = listOf()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
userViewModel.userModel.observe(this, Observer {
userList = it
})
userViewModel.onCreate()
setContent {
ListUsers(userList = userList)
}
}
}
ViewModel
class UserViewModel : ViewModel() {
val userModel = MutableLiveData<List<UserModel>>()
var getRandomUsersUseCase = RandomUsersUseCase()
fun onCreate() {
viewModelScope.launch {
val result = getRandomUsersUseCase()
if(!result.isNullOrEmpty()){
userModel.postValue(result)
}
}
}
}
Use State to ensure the data changes trigger recomposition of the Composable.
If you use another observable type such as LiveData in Compose, you
should convert it to State before reading it in a composable using
a composable extension function like LiveData.observeAsState().
Changes to your code would be,
val userListState by userViewModel.userModel.observeAsState()
setContent {
ListUsers(userList = userListState)
}
Why does it shows the data during rotation?
When rotating the screen or during any other configuration changes, the activity will be recreated.
More info on that here - Docs
In most cases, you would not require data to be changed when the screen rotates.
If you want to persist the data even after screen rotation, move the code inside onCreate() in your UserViewModel to the init block, like this.
init {
getData()
}
fun getData() {
viewModelScope.launch {
val result = getRandomUsersUseCase()
if(!result.isNullOrEmpty()){
userModel.postValue(result)
}
}
}
If you need to refresh the data on any other event like button click, swipe to refresh, etc, just call the getData() again on the event handler.
P.S: Check correct imports are added as required.
import androidx.compose.runtime.setValue
import androidx.compose.runtime.getValue

Can i use Jsoup in Jetpack Compose in kotlin

In traditional xml way , i use GlobalScope.launch{} with runOnUiThread {} to work with Jsoup.But in jetpack Compose this not work anymore. It just instant closed when run it.
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
GlobalScope.launch{
val url="somewebsite.com"
var doc= Jsoup.connect(url).get()
runOnUiThread {
}
}
setContent {
WannaJsoupTheme {
Surface(color = MaterialTheme.colors.background) {
Greeting("Android")
}
}
}
}
}
Explanation
Based on the above code, apparently you're using the main thread to perform I/O operations. Main thread are generally used for user interface operations. It's better to use background threads for I/O operations. - They are more efficient.
Here's demo app which I created to demonstrate use of JSoup with MVVM architecture.
Link - https://github.com/TheCodeMonks/NYTimes-App
Solution - https://gist.github.com/Spikeysanju/111e061ad82f3b7be6beb419fb18bca4
Quick Solution
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
// Initiate Coroutine scope
val scope = rememberCoroutineScope()
var text by remember {
mutableStateOf("")
}
JsoupTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background
) {
// Initiate coroutine scope will will run on IO thread & write method to get the text from the website
scope.launch(Dispatchers.IO) {
val url = "https://en.wikipedia.org/wiki/Steve_Jobs"
Jsoup.connect(url).get().also {
text = it.text()
}
}
// Fetch result from the state variable [Text]
Text(text = "Result: $text")
}
}
}
}
}

android livedata not working in Activity , viewmodel

Hello I have recently encountered a situation where observing is not possible in livedata, so I am going to ask a question
It's too basic, but I don't know why it doesn't work, so I need your help.
If you give me a little teaching, I would be grateful
my SignUpActivity
class SignUpActivity : BaseKotlinActivity<ActivitySignUpBindingImpl, SignUpViewModel>() {
override val layoutResourceId: Int get() = R.layout.activity_sign_up
override val viewModel: SignUpViewModel by viewModel()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = DataBindingUtil.setContentView<ActivitySignUpBindingImpl>(this, layoutResourceId)
binding.apply {
lifecycleOwner = this#SignUpActivity
signUpViewModel = viewModel
}
viewModel?.apply {
signUpStep.observe(this#SignUpActivity, Observer {
when (it) {
SignUpStep.SIGN_UP -> supportFragmentManager.beginTransaction().replace(R.id.fragment, SignUpFragment(), "SignUpFragment").commit()
SignUpStep.PASSWORD -> supportFragmentManager.beginTransaction().replace(R.id.fragment, SignUpPasswordFragment(), "SignUpPasswordFragment").commit()
SignUpStep.PHONE_CERTIFICATION -> supportFragmentManager.beginTransaction().replace(R.id.fragment, SignUpPhoneCertificationFragment(), "SignUpPhoneCertificationFragment").commit()
else -> Unit
}
Log.d("Test Checked1", "${signUpStep.value}")
})
}
}
}
my viewModel
private val _signUpStep = MutableLiveData<SignUpStep>(SignUpStep.SIGN_UP)
val signUpStep: LiveData<SignUpStep>
get() = _signUpStep
fun moveStep(view: View, newSignUpStep: SignUpStep) {
val oldSignUpStep = _signUpStep.value
_signUpStep.value = newSignUpStep
Log.d( "Test Checked","moveStep: $oldSignUpStep -> $newSignUpStep")
}
log
Test Checked1: SIGN_UP
moveStep: SIGN_UP -> PASSWORD
moveStep: PASSWORD -> PASSWORD
moveStep: PASSWORD -> PASSWORD
If you check the log, you can see that the moveStep changes normally. Then signUpSteop has changed normally, but it is not received in the observe of livedata, because the screen does not move and the log does not appear.
I'm just wondering if the code is wrong or what's wrong. Can you help me?
For reference, signUpStep is changing in Fragment and livedata is constantly being observed in activity.