How do I properly use onSaveInstanceState and onRestoreInstanceState with screen roation? - kotlin

I'm coding a simple counter app the tracks two different numbers depending on which button is pressed. Essentially just a counter app with two counters instead of one. I need the values to save when the screen is rotated.
Code:
`
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putInt("donkeyCounter", donkeyCounter)
outState.putInt("elephantCounter", elephantCounter)
}
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
super.onRestoreInstanceState(savedInstanceState)
val savedDonkeyCounter = savedInstanceState.getInt("donkeyCounter", donkeyCounter)
val savedElephantounter = savedInstanceState.getInt("elephantCounter", elephantCounter)
donkeyCounter = savedDonkeyCounter
elephantCounter = savedElephantounter
}
`
I've attempted to use the onSaveInstanceState to save the values of each counter, and then used onRestoreInstanceState to restore those values if the screen rotates. For some reason this does not work though, and both values get set back to zero regardless.

Related

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())
}

Android RecyclerView NotifyDataSetChanged with LiveData

When instantiating the ListAdapter for my RecyclerView, I call:
viewModel.currentList.observe(viewLifecycleOwner){
adapter.submitList(it)
}
which is what I've seen done in the Android Sunflower project as the proper way to submit LiveData to a RecyclerView. This works fine, and when I add items to my database they are updated in the RecyclerView. However, if I write
viewModel.currentList.observe(viewLifecycleOwner){
adapter.submitList(it)
adapter.notifyDataSetChanged()
}
Then my adapters data observer always lags behind. I call
adapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver(){
override fun onChanged() {
super.onChanged()
if (adapter.currentList.isEmpty()) {
empty_dataset_text_view.visibility = View.VISIBLE
} else {
empty_dataset_text_view.visibility = View.GONE
}
}
})
And empty_dataset_text_view appears one change after the list size reached zero, and likewise it disappears one change after the list size is non-zero.
Clearly, the observer is running the code before it has submitted it which in this case is LiveData queried from Room. I'd simply like to know what the workaround is for this: is there a way to "await" a return from my LiveData?

How to correctly update value of IntegerProperty in the view?

I am building simple application that will track a certain tabletop game of 2 players.
I have a view called MatchView
class MatchView : View() {
// data
private var currentRound = SimpleIntegerProperty(0)
private var currentTurn = SimpleIntegerProperty(0)
override val root = borderpane {
center = label(currentRound.stringBinding{ "Round %d".format(it) })
// other layout-related stuff
subscribe<EndTurnEvent> {
endPlayerTurn()
}
}
private fun endPlayerTurn() {
// increment turn
currentTurn.plus(1)
currentRound.plus(currentTurn.value % 2)
}
}
that is subscribed to EndTurnEvent - event emitted by one of the fragments used by view.
The called method is supposed to increment value of currentTurn and if needed currentRound (round increments after second player ends their turn)
However neither the value of currentRound nor the one of currentTurn are getting increased when i call .plus() method.
I have tried editting values differently :
private fun endPlayerTurn() {
// increment turn
currentTurn.value = currentTurn.value + 1
currentRound.value = currentTurn.value % 2
}
But this throws me java.lang.IllegalStateException: Not on FX application thread
I am aware that putting properties into views is anti-pattern, but since I just want to keep track of 2 properties, I thought I could put them directly into View
Platform.runLater(() -> {
// Update GUI from another Thread here
});

Handle To Remove/Add Live Data Observer to Observe On Button Click

I want to to observe a row in room database. it change after some period. but when we stop button click it need to be stop observing form database and when click start button it start observing again.
My current code is
To create Observer
private lateinit var recordObserver: Observer<Ride>
recordObserver= Observer<Ride> { rides ->
if (rides != null)
updateData(rides)
else
setDataToZero()
}
when(isState){
Constants.isrunning->{//need to start observer}
Constants.Stop->{//need to stop observer}
}
In order to start/stop observing LiveData you should use observe() / removeObserver() methods. As simple as that. If you have access to LifecycleOwner (Fragment, Activity) use fun observe(), if not - use fun observeForever().
Your code will look like this:
val liveData = database.observeRides() // get your live data
when(isState){
Constants.isrunning -> {
liveData.observe(this, recordObserver)
}
Constants.Stop -> {
liveData.removeObserver(recordObserver)
}
}

how to have loading in Kotlin

my MainActivity contains a ViewPager that loads 4 fragments, each fragment should load lots of data from the server.
so when my app wants to be run for the first time, it almost takes more than 3 seconds and the other times(for example, if you exit the app but not clean it from your 'recently app' window and reopen it) it takes almost 1 second.
while it is loading, it shows a white screen.
is there any way instead of showing a white screen till data become ready, I show my own image?
something like the splash page?
If you do long-running actions on the main thread, you risk getting an ANR crash.
Your layout for each fragment should have a loading view that is initially visible, and your data view. Something like this:
(not code)
FrameLayout
loading_view (can show a progress spinner or something, size is match parent)
content_view (probably a RecyclerView, initial visibility=GONE, size is match parent)
/FrameLayout
You need to do your long running action on a background thread or coroutine, and then swap the visibility of these two views when the data is ready to show in the UI.
You should not be directly handling the loading of data in your Fragment code, as Fragment is a UI controller. The Android Jetpack libraries provide the ViewModel class for this purpose. You would set up your ViewModel something like this. In this example, MyData could be anything. In your case it's likely a List or Set of something.
class MyBigDataViewModel(application: Application): AndroidViewModel(application) {
private val _myBigLiveData = MutableLiveData<MyData>()
val myBigLiveData: LiveData<MyData>() = _myBigLiveData
init {
loadMyBigData()
}
private fun loadMyBigData() {
viewModelScope.launch { // start a coroutine in the main UI thread
val myData: MyData = withContext(Dispatchers.Default) {
// code in this block is done on background coroutine
// Calculate MyData here and return it from lambda
// If you have a big for-loop, you might want to call yield()
// inside the loop to allow this job to be cancelled early if
// the Activity is closed before loading was finished.
//...
return#withContext calculatedData
}
// LiveData can only be accessed from the main UI thread so
// we do it outside the withContext block
_myBigLiveData.value = myData
}
}
}
Then in your fragment, you observe the live data to update the UI when it is ready. The below uses the fragment-ktx library, which you need to add to your project. You definitely should read the documentation on ViewModel.
class MyFragment: Fragment() {
// ViewModels should not be instantiated directly, or they won't be scoped to the
// UI life cycle correctly. The activityViewModels delegate handles instantiation for us.
private val model: MyBigDataViewModel by activityViewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
model.myBigLiveData.observe(this, Observer<MyData> { myData ->
loading_view.visibility = View.GONE
content_view.visibility = View.VISIBLE
// use myData to update the view content
})
}
}