Status Update
Comments
il...@google.com <il...@google.com> #2
Branch: androidx-master-dev
commit b90079595f33f58fece04026a97faa0d243acdb1
Author: Yuichi Araki <yaraki@google.com>
Date: Wed Sep 18 16:55:49 2019
Change the way to detect mismatch between POJO and query
This fixes cursor mismatch warnings with expandProjection.
Bug: 140759491
Test: QueryMethodProcessorTest
Change-Id: I7659002e5e0d1ef60fc1af2a625c0c36da0664d8
M room/compiler/src/main/kotlin/androidx/room/processor/QueryMethodProcessor.kt
M room/compiler/src/main/kotlin/androidx/room/solver/TypeAdapterStore.kt
M room/compiler/src/main/kotlin/androidx/room/solver/query/result/PojoRowAdapter.kt
M room/compiler/src/test/kotlin/androidx/room/processor/QueryMethodProcessorTest.kt
M room/compiler/src/test/kotlin/androidx/room/testing/TestProcessor.kt
il...@google.com <il...@google.com> #3
il...@google.com <il...@google.com> #4
Branch: androidx-master-dev
commit bdde5a1a970ddc9007b28de4aa29d60ffa588f08
Author: Yigit Boyar <yboyar@google.com>
Date: Thu Apr 16 16:47:05 2020
Re-factor how errors are dismissed when query is re-written
This CL changes how we handle errors/warnings if query is
re-written.
There was a bug in expandProjection where we would report warnings
for things that Room already fixes automatically (
The solution to that problem (I7659002e5e0d1ef60fc1af2a625c0c36da0664d8)
solved it by deferring validating of columns until after re-write
decision is made. Unfortunately, this required changing PojoRowAdapter
to have a dummy mapping until it is validating, make it hard to use
as it does have a non-null mapping which is not useful.
This CL partially reverts that change and instead rely on the log
deferring logic we have in Context. This way, we don't need to break
the stability of PojoRowAdapter while still having the ability to
drop warnings that room fixes. This will also play nicer when we
have different query re-writing options that can use more information
about the query results.
Bug: 153387066
Bug: 140759491
Test: existing tests pass
Change-Id: I2ec967c763d33d7a3ff02c1a13c6953b460d1e5f
M room/compiler/src/main/kotlin/androidx/room/log/RLog.kt
M room/compiler/src/main/kotlin/androidx/room/processor/QueryMethodProcessor.kt
M room/compiler/src/main/kotlin/androidx/room/solver/TypeAdapterStore.kt
M room/compiler/src/main/kotlin/androidx/room/solver/query/result/PojoRowAdapter.kt
il...@google.com <il...@google.com> #5
I lied, there's a second issue: AndroidViewBinding
always use the Activity's LayoutInflater
(i.e., LayoutInflater.from(context)
). This means that all inflating fragments are added to the Activity's FragmentManager
, thus completely breaking the save/restore of state when it comes to nesting Composables and AndroidViewBinding
inside another Fragment.
The updated project attached updates FragmentAwareAndroidViewBinding
to handle this case as well:
@Composable
fun <T : ViewBinding> FragmentAwareAndroidViewBinding(
bindingBlock: (LayoutInflater, ViewGroup, Boolean) -> T,
modifier: Modifier = Modifier,
update: T.() -> Unit = {}
) {
val fragmentContainerViews = remember { mutableStateListOf<FragmentContainerView>() }
val localView = LocalView.current
AndroidViewBinding({ parentInflater, parent, attachToRoot ->
// Find the right FragmentManager
val inflater = try {
val parentFragment = localView.findFragment<Fragment>()
parentFragment.layoutInflater
} catch (e: Exception) {
parentInflater
}
bindingBlock(inflater, parent, attachToRoot)
}, modifier = modifier) {
fragmentContainerViews.clear()
val rootGroup = root as? ViewGroup
if (rootGroup != null) {
findFragmentContainerViews(rootGroup, fragmentContainerViews)
}
update()
}
val activity = LocalContext.current as FragmentActivity
fragmentContainerViews.forEach { container ->
DisposableEffect(container) {
// Find the right FragmentManager
val fragmentManager = try {
val parentFragment = container.findFragment<Fragment>()
parentFragment.childFragmentManager
} catch (e: Exception) {
activity.supportFragmentManager
}
// Now find the fragment inflated via the FragmentContainerView
val existingFragment = fragmentManager.findFragmentById(R.id.fragment_container_view)
onDispose {
if (existingFragment != null && !fragmentManager.isStateSaved) {
// If the state isn't saved, that means that some state change
// has removed this Composable from the hierarchy
fragmentManager.commit {
remove(existingFragment)
}
}
}
}
}
}
il...@google.com <il...@google.com>
ap...@google.com <ap...@google.com> #6
Branch: androidx-main
commit fcb9b1a8b38ea68d345b8f59b54231c34ea6a75e
Author: Ian Lake <ilake@google.com>
Date: Thu Mar 18 15:42:36 2021
Ensure AndroidViewBinding+Fragments work after recreate
When a Fragment is inflated via AndroidViewBinding,
it should consistently be attached to the view hierarchy
even after a configuration change.
By adding an implementation dependency on a new enough
version of fragments that fixes the underlying issue,
we can ensure that AndroidViewBinding always works.
Relnote: "Compose ViewBinding now depends on
[Fragment `1.3.2`](/jetpack/androidx/releases/fragment#1.3.2)
and now consistently shows fragments inflated via
`FragmentContainerView` after configuration changes."
Test: new FragmentRecreateTest
BUG: 179915946
Change-Id: I0743d383564ee28429ad0074f58de79c0e98ada0
M compose/ui/ui-viewbinding/build.gradle
M compose/ui/ui-viewbinding/samples/build.gradle
M compose/ui/ui-viewbinding/samples/src/androidTest/AndroidManifest.xml
A compose/ui/ui-viewbinding/samples/src/androidTest/java/androidx/compose/ui/samples/FragmentRecreateTest.kt
A compose/ui/ui-viewbinding/samples/src/androidTest/java/androidx/compose/ui/samples/InflatedFragment.kt
A compose/ui/ui-viewbinding/samples/src/main/res/layout/test_fragment_layout.xml
ap...@google.com <ap...@google.com> #7
Branch: androidx-main
commit 0934428c2e01d225bbf29485ec539ae60ea88c98
Author: Ian Lake <ilake@google.com>
Date: Thu Mar 18 16:55:12 2021
Ensure AndroidViewBinding uses the right LayoutInflater
Fragments rely on nesting fragments when it comes to
saving and restoring their state properly. When using
LayoutInflater.from(context), any fragments inflated via
FragmentContainerView are always attached to the
Activity's FragmentManager.
This leads to issues when your Compose hierarchy is
inflated as part of a Fragment. In these cases, we
specifically want any AndroidViewBinding inflated
fragments to be added as child fragments. We can
accomplish this by checking whether the LocalView
is owned by a fragment and, if so, use that fragment's
LayoutInflater (this is the magic that causes inflation
to inflate fragments into the right FragmentManager).
Relnote: "`AndroidViewBinding` now correctly nests
fragments inflated via `FragmentContainerView` when
your `ComposeView` is within a `Fragment`, fixing
issues with saving and restoring the state of those
fragments."
Test: new testRecreateChildFragment test
BUG: 179915946
Change-Id: I70eb0831fb6b756e2968e713e181193969d49b9a
M compose/ui/ui-viewbinding/samples/src/androidTest/AndroidManifest.xml
M compose/ui/ui-viewbinding/samples/src/androidTest/java/androidx/compose/ui/samples/FragmentRecreateTest.kt
M compose/ui/ui-viewbinding/src/main/java/androidx/compose/ui/viewinterop/AndroidViewBinding.kt
ap...@google.com <ap...@google.com> #8
Branch: androidx-main
commit 9ad171525dfbb4a5393538a87f32567bcc2f4960
Author: Ian Lake <ilake@google.com>
Date: Thu Mar 18 22:14:33 2021
Remove fragments inflated from AndroidViewBinding
When an AndroidViewBinding is disposed, it is
responsible for cleaning up its state. Unlike
regular views, any fragments inflated from a
FragmentContainerView must be manually cleaned
by removing them from the FragmentManager.
This avoids cases where repeatedly adding and
removing an AndroidViewBinding from the hierarchy
creates more and more fragment instances or
re-uses fragment instances with state that should
have been disposed of with the containing composable.
Relnote: "`AndroidViewBinding` now properly removes
fragments inflated via `FragmentContainerView` when
the `AndroidViewBinding` is removed from the compose
hierarchy."
Test: FragmentRemoveTest suite
BUG: 179915946
Change-Id: Ib02480f78570972b3190ebd45ab12f4d7291fa23
M compose/ui/ui-viewbinding/build.gradle
M compose/ui/ui-viewbinding/samples/src/androidTest/AndroidManifest.xml
A compose/ui/ui-viewbinding/samples/src/androidTest/java/androidx/compose/ui/samples/FragmentRemoveTest.kt
M compose/ui/ui-viewbinding/src/main/java/androidx/compose/ui/viewinterop/AndroidViewBinding.kt
il...@google.com <il...@google.com> #9
Verified with a androidx.compose.ui:ui-viewbinding:1.0.0-SNAPSHOT
with build 7236835
fixes all of the issues in the above FragmentInCompose
app.
This means that you can use AndroidViewBinding
directly with layouts that inflate fragments via FragmentContainerView
. I'd suggest inflating fragments into your Compose hierarchy only as a transitional step and not as a suggested final architecture. :-)
Note: the <fragment>
tag should never be used in any app, much less an app built on Compose. It is expected that inflating the same <fragment>
layout twice will still crash your app.
sn...@gmail.com <sn...@gmail.com> #10
is this issue really resolved ?
I still face the same issue .
Here is my code , is there anything I am missing out ?
Scaffold(topBar = { TopAppBar(topAppBarData = topBar) })
{
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
AndroidViewBinding(TestFragmentBinding::inflate)
}
}
il...@google.com <il...@google.com> #11
Re
ro...@theorytank.com <ro...@theorytank.com> #12
I think the problem is that the onRelease code assumes that the localContext (see --->) can be cast to an Activity. But this doesn't work when it's a ContextThemeWrapper.
onRelease = { view ->
view.getBinding<T>().onRelease()
(view as? ViewGroup)?.let { rootGroup ->
// clean up inflated fragments when the AndroidViewBinding is disposed
// Find the right FragmentManager
val fragmentManager = parentFragment?.childFragmentManager
---> ?: (localContext as? FragmentActivity)?.supportFragmentManager
forEachFragmentContainerView(rootGroup) { container ->
// Now find the fragment inflated via the FragmentContainerView
val existingFragment = fragmentManager?.findFragmentById(container.id)
if (existingFragment != null && !fragmentManager.isStateSaved) {
// If the state isn't saved, that means that some state change
// has removed this Composable from the hierarchy
fragmentManager.commitNow {
remove(existingFragment)
}
}
}
}
},
il...@google.com <il...@google.com> #13
Re AndroidFragment
fragment-compose
artifact, which is what we suggest for embedding your fragment in a Compose hierarchy now.
[Deleted User] <[Deleted User]> #14
Re
Description
Jetpack Compose release version: Compose alpha11 Android Studio Build: Arctic Fox Canary 4
There appears to be a number of issues with inflating a fragment by using
AndroidViewBinding
:If you use
FragmentContainerView
, the fragment appears the first time the container is inflated, but later times it does not appear.If you use
<fragment>
, the fragment appears the first time the fragment is inflated, but will crash if you try to re-inflate the same layout a second time. Rotating your screen or otherwise recreating the Activity will reset it so that the first inflation worksIn both cases, removing the
AndroidViewBinding
from your compose hierarchy does not remove the underlyingFragment
from theFragmentManager
. This means that the Fragment remains in memory forever afterwards.I've attached a sample project that contains three situation (using
FragmentContainerView
, using<fragment>
, and manually doing aFragmentTransaction
in Compose code) and a 'Show' button of each that lets you confirm what happens when theAndroidViewBinding
is removed from the hierarchy.My investigation lead to a couple of discoveries:
After a configuration change, the whole activity and the
FragmentManager
has moved to theRESUMED
state before the compose hierarchy is processed andAndroidViewBinding
is able to inflate its container. This means the original fragment added via the inflatedFragmentContainerView
has already had its view created and moved to theRESUMED
state before the container exists. Thus, the transition betweenonCreateView()
andonViewCreated()
where the view is attached to its container has already passed when theAndroidViewBinding
creates the container.The
<fragment>
tag added fragment never goes above theCREATED
state on its own - Fragments is correctly stopping it from going higher thanCREATED
during the inflation, but because Compose is inflating it after the FragmentManager has already moved throughRESUMED
, the usual moving of state for<fragment>
inflated fragments doesn't happen. Doing anotherFragmentTransaction
on a different fragment/container moves it toSTARTED
, but it never moves toRESUMED
as no FragmentTransactions on that container are pending (and moving toRESUMED
is done at a container by container basis) as the<fragment>
tag doesn't actually do aFragmentTransaction
.It is expected that when a composable is removed from the hierarchy that any internal state is thrown out (that's why you'd normally hoist state you want to save). Because Fragments are never removed when the
AndroidViewBinding
is removed from the hierarchy, the Fragments keep their state over a removal / addition of a new element that happens to inflate that same layout.