We have a legacy code base that uses 1.0.0 AppCompatActivity. Our team wants to use Jetpack Compose UI components in a new feature we want to develop. This new UI component would be a popover over the activity content. The popover is not full-screen and has shadows around it. In order to do that, we created a full screen Dialog Fragment that utilizes a composable as follows:
class MyDialogFragment : AppCompatDialogFragment() {
private val dismiss: () -> Unit = { this.dismiss() }
private lateinit var tapPoint: Point
fun initialize(tapPoint: Point) {
this.tapPoint = tapPoint
}
@OptIn(ExperimentalPagerApi::class)
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
return ComposeView(requireContext()).apply {
setViewCompositionStrategy(DisposeOnViewTreeLifecycleDestroyed)
setContent {
MyDialogScreen(
dismiss = dismiss
)
}
}
}
fun show(manager: FragmentManager) {
super.show(manager, TAG)
// the style only makes sure the fragment is displayed full-screen
// <style name="MyDialogStyle">
// <item name="android:layout_width">match_parent</item>
// <item name="android:layout_height">wrap_content</item>
// <item name="android:windowSoftInputMode">adjustResize</item>
// </style>
this.setStyle(DialogFragment.STYLE_NO_TITLE, R.style.MyDialogStyle)
}
override fun onStart() {
super.onStart()
// This makes sure the full-screen dialog has a transparent background, so that the activity content could be seen behind the popover.
dialog?.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
dialog?.window?.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
}
companion object {
const val TAG: String = "My Dialog"
fun newInstance(tapPoint: Point): NotepadDialogFragment {
return NotepadDialogFragment()
}
}
}
The simplified composable is as follows:
@Composable
fun MyDialogScreen(
dismiss: () -> Unit,
tapPoint: Point
) {
val windowInfo = rememberWindowInfo()
MyTheme(
windowInfo = windowInfo,
) {
Column(modifier = Modifier.fillMaxSize()) {
if (shouldPinToBottom(windowInfo = windowInfo, tapPoint = tapPoint)) {
Spacer(modifier = Modifier.weight(1f))
}
Column(
modifier = Modifier
.align(Alignment.CenterHorizontally)
.width(width = LocalCustomShape.current.width)
.height(height = LocalCustomShape.current.height)
.aspectRatio(
ratio = LocalCustomShape.current.width / LocalCustomShape.current.height,
matchHeightConstraintsFirst = true
)
.padding(LocalCustomShape.current.padding)
.drawColoredShadow(
color = LocalCustomColors.current.shadowColor,
shadowRadius = LocalCustomShape.current.shadowRadius,
cornerRadius = LocalCustomShape.current.cornerRadius
)
) {
MyDialogContent()
}
}
}
}
@Composable
fun MyDialogContent() {
Column(
modifier = Modifier
.fillMaxSize()
.background(Color.White)
.clip(RoundedCornerShape(LocalCustomShape.current.cornerRadius))){
BasicTextField(
value = "Hello world",
onValueChange = {},
modifier = Modifier
.background(LocalCustomColors.current.textEditorBackground)
.fillMaxWidth()
.fillMaxHeight()
.padding(all = 16.dp),
textStyle = LocalCustomTypography.current.textEditor,
)
}
}
private fun shouldPinToBottom(windowInfo: MyWindowInfo, tapPoint: Point): Boolean {
return windowInfo.screenWidthInfo != MyWindowInfo.WindowType.Compact
&& windowInfo.screenHeightInfo != MyWindowInfo.WindowType.Compact
&& tapPoint.y < windowInfo.screenHeight * HEIGHT_RATIO
}
The window info is a data class containing window info such as screen height/width, whether its compact/medium/expanded size, or whether it is currently in landscape mode. MyTheme takes in window info and calculates the desired height of the popover. The Composable gets the custom theme specs from things like LocalCustomShape.current.shadowRadius. I tried to open this dialog fragment from the activity on my Android 11 and Android 9 tablets and it worked perfectly.
However, when testing the same code on Android phones (including an Android 9 and an Android 12 phone), a weird behavior happens -- the dialog shadow darkens with each user touch on the text edit view. Initially I thought it was because I used a custom modifier to draw the shadows, but after switching to use the built-in shadow modifier, the same issue still happens. With a little more investigation and playing around, it is highly possible that this is caused by the same view being rendered multiple times on the screen.
- The video that shows the shadow darkens with user touch: https://drive.google.com/file/d/1QfluyN4SGqSjbZDYgU842CU245n2ts1-/view?usp=sharing
- If the popover is resized with keyboard opened, the previous view remains on the page: https://drive.google.com/file/d/1mTWmbumnMB4lNLH5Mc7J_g_g51RKBCgt/view?usp=sharing
Need to mention that in App Inspector, there is always only just one Composable. I also checked recomposition using the method in this post, and it looked normal.
Also need to mention that if I open the same app on simulator phones, worse things happen -- sometimes over 10 such popover views are presented on the screen, sometimes the whole activity could turn upside down when the dialog is opened. This also happens in App Inspector:
The compose version in use is 1.0.5, appcompat version 1.0.0, kotlin version 1.5.31, core ktx version 1.3.2, constraint layout version 1.1.3, AGP version 7.0.2, Gradle version 7.4.2. I tried other compose versions (from 1.0.0 to 1.2.0 beta) but they don't work either.
Also did a memory profiling:
Memory keeps increasing for pixel 4: native memory increases from 158 to 178, java from 54 to 102.5 mb:

A Samsung tablet has some memory fluctuations but overall it stays stable.
Has anyone ever seen this issue before? I exhausted possible solutions I could think of but to no avail. I am not familiar with how compose view is rendered on phones and why this issue only happened on my phones not tablets. Could someone help me with this issue?
