onApplyWindowInsets(WindowInsets insets) not getting called - kotlin

I am currently using a custom view that extends a Constraint layout but I it does not trigger this overridden method in the view onApplyWindowInsets(WindowInsets insets) not sure what went missing.
class TestCustomView #JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : ConstraintLayout(context, attrs, defStyleAttr) {
init {
}
//This method one not get called
override fun onApplyWindowInsets(insets: WindowInsets): WindowInsets {
return super.onApplyWindowInsets(insets)
val statusBarHeight = insets.systemWindowInsetTop
}
override fun fitSystemWindows(insets: Rect): Boolean {
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT) {
// Intentionally do not modify the bottom inset. For some reason,
// if the bottom inset is modified, window resizing stops working.
insets.left = 0
insets.top = 0
insets.right = 0
}
return super.fitSystemWindows(insets)
}
}

Once the insets are consumed, propagation down the hierarchy stops. It looks like something higher up is consuming what's available. See isConsumed() of WindowsInset.
Check if these insets have been fully consumed.
Insets are considered "consumed" if the applicable consume* methods have been called such that all insets have been set to zero. This affects propagation of insets through the view hierarchy; insets that have not been fully consumed will continue to propagate down to child views.

I found that without the android:windowTranslucentStatus property set to true, onApplyWindowInsets is never called. I discovered this on reading this unanswered question.
In styles.xml:
<style name="ExampleTheme" parent="Theme.AppCompat.NoActionBar">
<item name="android:windowTranslucentStatus">true</item>
</style>
I would love to understand why this is so, and if there is a way to have it called without requiring this attribute change.

I think if you wanna reset it's windowInsets callback state, you should change sysUiVisibility params to let fitsSystemWindows work normally. Because fitsSystemWindows is a key to consume the windowInsets. You can learn more from android source code, of course, you need time.

Related

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

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

updating label with progress of http post not working. IllegalStateException

I am having trouble with binding a UI component to an observable that gets updated progress from a http post event. I get an IllegalStateException
As I understand it the issue is the bind update is not happening on the UI thread. The answers I have read say that I need to use runAsync and then specify a UI block to update the UI component, but I am at a loss for how to accomplish this.
// View class
private val controller: ZumController by inject()
item("_Upload") {
isMnemonicParsing = true
action {
controller.uploadToServer()
}
}
bottom = label() {
useMaxWidth = true
padding = Insets(5.0, 10.0, 5.0, 10.0)
this.bind(controller.progress)
}
// Controller class
var progress = SimpleStringProperty("Select images to upload")
fun uploadToServer() {
images.forEach{ p ->
Fuel.upload("http://127.0.0.1:8089")
.add {FileDataPart(File(p), name = "file")}
.progress { readBytes, totalBytes ->
progress.value = (readBytes.toFloat() / totalBytes.toFloat() * 100).toString()}
.response { _ -> }
}
}
How would I go about making sure the UI is updated during the application thread when I need progress before function call (uploadToServer()) returns? Sorry if this has already been answered, I still don't get exactly what is happening here.
I've solved my problem with the following changes. I pass the FXTask to function uploadToServer(). There I updateMessage() with the progress callback for the http POST request. I can't say its the best way but it works. feel free to update this answer with more clear and concise code
item("_Upload") {
isMnemonicParsing = true
action {
runAsync {
controller.uploadToServer(this)
} ui {
}
}
}
fun uploadToServer(task: FXTask<*>) {
images.forEach{ p ->
Fuel.upload("http://127.0.0.1:8089")
.add {FileDataPart(File(p), name = "file")}
.progress { readBytes, totalBytes ->
val perComplete = readBytes.toFloat() / totalBytes.toFloat() * 100
task.updateMessage("Uploading $p %.2f".format(perComplete).plus("%"))
}
.response { _ -> }
}
}
TornadoFX has a built in TaskStatus object which has properties for the progress of the task. You can bind one or more of the properties in the TaskStatus object to any UI element, and simply call updateProgress from within your controller function. You don't even need to pass in the TaskStatus object, as the default instance will be used if you don't.
There are a few test appa within the framework that does this:
https://github.com/edvin/tornadofx/blob/master/src/test/kotlin/tornadofx/testapps/AsyncProgressApp.kt
https://github.com/edvin/tornadofx/blob/master/src/test/kotlin/tornadofx/testapps/TaskStatusTest.kt
That said, a quick and dirty solution for updating the UI from any other thread is simply wrapping the UI manipulation code inside runLater {}. This will work equally well for just updating a label for example.