WindowMetricsCalculator deprecated or what happen to WindowManager - android

My problems is simple I am using this library
implementation "androidx.window:window:1.0.0"
before it was working well but now I can't import WindowMetricsCalculator, does it deprecated or what ??, there is alternative way to calculate WindowMetrics ??
private void computeWindowSizeClasses() {
WindowMetrics metrics = WindowMetricsCalculator.getOrCreate()
.computeCurrentWindowMetrics(this);
}

If you're using Jetpack Compose, you can call this method:
in androidx/compose/material3/windowsizeclass/AndroidWindowSizeClass.android.kt
#ExperimentalMaterial3WindowSizeClassApi
#Composable
fun calculateWindowSizeClass(activity: Activity): WindowSizeClass {
// Observe view configuration changes and recalculate the size class on each change. We can't
// use Activity#onConfigurationChanged as this will sometimes fail to be called on different
// API levels, hence why this function needs to be #Composable so we can observe the
// ComposeView's configuration changes.
LocalConfiguration.current
val density = LocalDensity.current
val metrics = WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(activity)
val size = with(density) { metrics.bounds.toComposeRect().size.toDpSize() }
return WindowSizeClass.calculateFromSize(size)
}

Related

Jetpack Compose: Save composable size before configuration changes, then use that size after configuration changes

I am saving the size of a composable using the onSizeChanged modifier. I want to know what the size of the composable was during the previous configuration so that when the configuration changes, I can do a calculation with that size. However, I want to wait until my calculation finishes before I start saving the size again based on the new configuration.
Right now I'm passing the size to MyComposable. MyComposable is the only one who cares about what the size was during the last configuration (landscape or portrait), so I thought I would save the size within its scope. I have no idea when the configuration change happens, so I keep updating a variable oldSize whenever there is a new size to hopefully save the most recent value before the configuration changes.
Below is the code of how I manage the size within MyComposable. It seems to work, but is there a more straightforward way of doing this?
#Composable
fun MyComposable(
size: () -> IntSize,
) {
// save size across configurations
var oldSize by rememberSaveable(stateSaver = IntSizeSaver) {
mutableStateOf(IntSize.Zero)
}
var updateSize by remember { mutableStateOf(false) }
if (updateSize) {
LaunchedEffect(size()) { oldSize = size() }
}
LaunchedEffect(true) {
// do stuff with oldSize...
myFunction(oldSize)
// allow oldSize to be updated again
updateSize = true
}
}
I am guessing if I wanted to put some of this logic into some kind of composable function, it would look something like this. I used rememberUpdatedState as a guide.
#Composable
fun <T> rememberUpdatedStateSaveable(
newValue: T,
stateSaver: Saver<T, out Any>,
enableUpdates: Boolean
): State<T> = rememberSaveable(stateSaver = stateSaver) {
mutableStateOf(newValue)
}.apply { value = if (enableUpdates) newValue else value }

Jetpack Compose : How to overlay Composable with AndroidView?

I'm new to Jetpack Compose and trying to figure out how to solve next task:
I need to create a simple transparent AndroidView. And it needs to be used as an overlay for Composable functions.
The problem is that an overlay should be the same size as a compose view under it.
I had some-kind of successful attempt with this:
#Composable
fun BugseeOverlayView() {
AndroidView(
modifier = Modifier.fillMaxSize(),
factory = { ctx ->
View(ctx).apply {
layoutParams = LinearLayout.LayoutParams(200, 200) //Hardcoded size
alpha = 0.0F
}
}, update = {
Bugsee.addSecureView(it) // 3rd party I need to use
}
)
}
And then I used it like:
Box {
Box(Modifier.fillMaxSize()) {
BugseeOverlayView()
}
Text("Hide me") // or some 'CustomComposableView(param)'
}
This works, but the size is hardcoded.
PS. I need an AndroidView because of third-party tool which accepts android.view.View as a parameter.
You can get size of a Composable in various ways.
1- Modifier.onSizeChanged{intSize->} will return Composable size in pixels you can convert this to dp using LocalDensity.current.run{}. With this approach the size you set will change and there needs to be another recomposition. You can also get size of a Composable from Modifier.onGloballyPositioned either.
val density = LocalDensity.current
var dpSize: DpSize by remember{ mutableStateOf(DpSize.Zero) }
Modifier.onSizeChanged { size: IntSize ->
density.run { dpSize = DpSize(size.width.toDp(), size.height.toDp()) }
}
Modifier.onGloballyPositioned {layoutCoordinates: LayoutCoordinates ->
val size = layoutCoordinates.size
density.run { dpSize = DpSize(size.width.toDp(), size.height.toDp()) }
}
2- If the Composable has fixed size or covers screen you can use
BoxWithConstraints {
SomeComposable()
AndroidView(modifier=Modifier.size(maxWidth, maxHeight)
}
3- If you don't have chance to get Composable size and don't want to have another recomposition you can use SubcomposeLayout. Detailed answer is available here how to create a SubcomposeLayout to get exact size of a Composable without recomposition.
When you are able to get size of Composable you can set same size to AndroidView and set layout params to match parent. If that's not what you wish you can still set Modifier.fillMaxSize while using methods above to set layout params

Using Jetpack WindowManager to calculate screen size on Android

As Display methods are deprecated in Android 12, I am planning to use Jetpack's backward compatible WindowManager library succeeding Display.
However I am not sure whether I face an issue if I directly access the sizes of a screen in Activity like below:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val windowMetrics = WindowMetricsCalculator.getOrCreate()
.computeCurrentWindowMetrics(this#WindowMetricsActivity)
val width = windowMetrics.bounds.width()
val height = windowMetrics.bounds.height()
}
Because Google's sample code insists using onConfigurationChanged method by using a utility container view like below:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Add a utility view to the container to hook into
// View.onConfigurationChanged.
// This is required for all activities, even those that don't
// handle configuration changes.
// We also can't use Activity.onConfigurationChanged, since there
// are situations where that won't be called when the configuration
// changes.
// View.onConfigurationChanged is called in those scenarios.
// https://issuetracker.google.com/202338815
container.addView(object : View(this) {
override fun onConfigurationChanged(newConfig: Configuration?) {
super.onConfigurationChanged(newConfig)
logCurrentWindowMetrics("Config.Change")
}
})
}
#SuppressLint("NotifyDataSetChanged")
private fun logCurrentWindowMetrics(tag: String) {
val windowMetrics = WindowMetricsCalculator.getOrCreate()
.computeCurrentWindowMetrics(this#WindowMetricsActivity)
val width = windowMetrics.bounds.width()
val height = windowMetrics.bounds.height()
adapter.append(tag, "width: $width, height: $height")
runOnUiThread {
adapter.notifyDataSetChanged()
}
}
In our project, we directly access the sizes of screens and I am wondering how we migrate to accessing them after onConfigurationChanged invokes and emits the size values by using MAD skills.
Any approaches will be appreciated
I use the following method to get a screen size starting from Android 11 (API level 30):
fun getScreenSize(context: Context): Size {
val metrics: WindowMetrics = context.getSystemService(WindowManager::class.java).currentWindowMetrics
return Size(metrics.bounds.width(), metrics.bounds.height())
}

Jetpack Compose: What is the best way to support all screen sizes?

I searched on Google multiple ways to support multiple screen sizes on Android with Jetpack compose and I finally found the Google documentation:
https://developer.android.com/guide/topics/large-screens/support-different-screen-sizes#compose
enum class WindowSizeClass { COMPACT, MEDIUM, EXPANDED }
#Composable
fun Activity.rememberWindowSizeClass() {
val configuration = LocalConfiguration.current
val windowMetrics = remember(configuration) {
WindowMetricsCalculator.getOrCreate()
.computeCurrentWindowMetrics(this)
}
val windowDpSize = with(LocalDensity.current) {
windowMetrics.bounds.toComposeRect().size.toDpSize()
}
val widthWindowSizeClass = when {
windowDpSize.width < 600.dp -> WindowSizeClass.COMPACT
windowDpSize.width < 840.dp -> WindowSizeClass.MEDIUM
else -> WindowSizeClass.EXPANDED
}
val heightWindowSizeClass = when {
windowDpSize.height < 480.dp -> WindowSizeClass.COMPACT
windowDpSize.height < 900.dp -> WindowSizeClass.MEDIUM
else -> WindowSizeClass.EXPANDED
}
// Use widthWindowSizeClass and heightWindowSizeClass
}
But it might be a problem for ldpi screens and where to store those variables? Do I need to do same as the old way and store dimens value in a dimen folder for all densities? Because for example images in a screen on 400dp might look very big on ldpi screen (~120dp)
I'm quite confusing and I'm new to jetpack compose. Thanks in advance for your help.
You can use library https://github.com/GetStream/butterfly
or
create
rememberWindowSizeClass.kt
data class WindowSizeClass(
val widthWindowSizeClass: WindowType,
val heightWindowSizeClass: WindowType,
val widthWindowDpSize: Dp,
val heightWindowDpSize: Dp
) {
sealed class WindowType {
object COMPACT : WindowType()
object MEDIUM : WindowType()
object EXPANDED : WindowType()
}
}
#Composable
fun Activity.rememberWindowSizeClass(): WindowSizeClass {
val configuration = LocalConfiguration.current
val windowMetrics = remember(configuration) {
WindowMetricsCalculator.getOrCreate()
.computeCurrentWindowMetrics(activity = this)
}
val windowDpSize = with(LocalDensity.current) {
windowMetrics.bounds.toComposeRect().size.toDpSize()
}
return WindowSizeClass(
widthWindowSizeClass = when {
windowDpSize.width < 0.dp -> throw IllegalArgumentException("Dp value cannot be negative")
windowDpSize.width < 600.dp -> WindowSizeClass.WindowType.COMPACT
windowDpSize.width < 840.dp -> WindowSizeClass.WindowType.MEDIUM
else -> WindowSizeClass.WindowType.EXPANDED
},
heightWindowSizeClass = when {
windowDpSize.height < 0.dp -> throw IllegalArgumentException("Dp value cannot be negative")
windowDpSize.height < 480.dp -> WindowSizeClass.WindowType.COMPACT
windowDpSize.height < 900.dp -> WindowSizeClass.WindowType.MEDIUM
else -> WindowSizeClass.WindowType.EXPANDED
},
widthWindowDpSize = windowDpSize.width,
heightWindowDpSize = windowDpSize.height
)
}
In
MainActivity.kt
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
val windowSize = rememberWindowSizeClass()
when (windowSize.widthWindowSizeClass) {
is WindowSizeClass.WindowType.COMPACT -> {
AppScreenCompact()
}
is WindowSizeClass.WindowType.MEDIUM -> {
AppScreenMedium()
}
else -> {
AppScreenExpanded()
}
}
}
}
}
For further info refer to Documentation and Sample
The code above is just an example. You can create your own code based on this idea to match your needs.
You can use the resources system to save the dimensions but I don't really see a reason to do so. If your image is 200dp, it is meant to be 200dp. DP is dynamic. Yes, 200dp will be larger on a small screen but it should because the user is meant to actually see the content of the image.
Your app layout not breaking on different screen sizes and settings is another issue that isn't really that related to compose in theory. You just need to design your layouts in a way that they are not too sensitive to font/dpi size changes meaning a mild font/dpi size change should not be able to break the layout.
Other than this, you can just write different composables for screen configurations that are too different to work with your standard composable.
Use Jetmagic. It was designed to treat your composables like resources in the same way the older view system treated your xml layouts. It supports all the configuration qualifiers that are supported by resource folders including the screen size and screen density qualifiers.
Using Jetmagic, your composable will be selected based upon device configurations and reselect a different composable should the configuration change at runtime such as device orientation changes.
The framework includes a demo app and there's a detailed article on Medium about it:
https://github.com/JohannBlake/Jetmagic
Jetmagic is a far superior solution than using the WindowSizeClass.

Jetpack Compose: Make full-screen (absolutely positioned) component

How can I go about making a composable deep down within the render tree full screen, similar to how the Dialog composable works?
Say, for example, when a use clicks an image it shows a full-screen preview of the image without changing the current route.
I could do this in CSS with position: absolute or position: fixed but how would I go about doing this in Jetpack Compose? Is it even possible?
One solution would be to have a composable at the top of the tree that can be passed another composable as an argument from somewhere else in the tree, but this sounds kind of messy. Surely there is a better way.
From what I can tell you want to be able to draw from a nested hierarchy without being limited by the parent constraints.
We faced similar issues and looked at the implementation how Composables such as Popup, DropDown and Dialog function.
What they do is add an entirely new ComposeView to the Window.
Because of this they are basically starting from a blank canvas.
By making it transparent it looks like the Dialog/Popup/DropDown appears on top.
Unfortunately we could not find a Composable that provides us the functionality to just add a new ComposeView to the Window so we copied the relevant parts and made following.
#Composable
fun FullScreen(content: #Composable () -> Unit) {
val view = LocalView.current
val parentComposition = rememberCompositionContext()
val currentContent by rememberUpdatedState(content)
val id = rememberSaveable { UUID.randomUUID() }
val fullScreenLayout = remember {
FullScreenLayout(
view,
id
).apply {
setContent(parentComposition) {
currentContent()
}
}
}
DisposableEffect(fullScreenLayout) {
fullScreenLayout.show()
onDispose { fullScreenLayout.dismiss() }
}
}
#SuppressLint("ViewConstructor")
private class FullScreenLayout(
private val composeView: View,
uniqueId: UUID
) : AbstractComposeView(composeView.context) {
private val windowManager =
composeView.context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
private val params = createLayoutParams()
override var shouldCreateCompositionOnAttachedToWindow: Boolean = false
private set
init {
id = android.R.id.content
ViewTreeLifecycleOwner.set(this, ViewTreeLifecycleOwner.get(composeView))
ViewTreeViewModelStoreOwner.set(this, ViewTreeViewModelStoreOwner.get(composeView))
ViewTreeSavedStateRegistryOwner.set(this, ViewTreeSavedStateRegistryOwner.get(composeView))
setTag(R.id.compose_view_saveable_id_tag, "CustomLayout:$uniqueId")
}
private var content: #Composable () -> Unit by mutableStateOf({})
#Composable
override fun Content() {
content()
}
fun setContent(parent: CompositionContext, content: #Composable () -> Unit) {
setParentCompositionContext(parent)
this.content = content
shouldCreateCompositionOnAttachedToWindow = true
}
private fun createLayoutParams(): WindowManager.LayoutParams =
WindowManager.LayoutParams().apply {
type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL
token = composeView.applicationWindowToken
width = WindowManager.LayoutParams.MATCH_PARENT
height = WindowManager.LayoutParams.MATCH_PARENT
format = PixelFormat.TRANSLUCENT
flags = WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS or
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
}
fun show() {
windowManager.addView(this, params)
}
fun dismiss() {
disposeComposition()
ViewTreeLifecycleOwner.set(this, null)
windowManager.removeViewImmediate(this)
}
}
Here is an example how you can use it
#Composable
internal fun Screen() {
Column(
Modifier
.fillMaxSize()
.background(Color.Red)
) {
Text("Hello World")
Box(Modifier.size(100.dp).background(Color.Yellow)) {
DeeplyNestedComposable()
}
}
}
#Composable
fun DeeplyNestedComposable() {
var showFullScreenSomething by remember { mutableStateOf(false) }
TextButton(onClick = { showFullScreenSomething = true }) {
Text("Show full screen content")
}
if (showFullScreenSomething) {
FullScreen {
Box(
Modifier
.fillMaxSize()
.background(Color.Green)
) {
Text("Full screen text", Modifier.align(Alignment.Center))
TextButton(onClick = { showFullScreenSomething = false }) {
Text("Close")
}
}
}
}
}
The yellow box has set some constraints, which would prevent the Composables from inside to draw outside its bounds.
Using the Dialog composable, I have been able to get a proper fullscreen Composable in any nested one. It's quicker and easier than some of other answers.
Dialog(
onDismissRequest = { /* Do something when back button pressed */ },
properties = DialogProperties(dismissOnBackPress = true, dismissOnClickOutside = false, usePlatformDefaultWidth = false)
){
/* Your full screen content */
}
If I understand correctly you just don't want to navigate anywhere. Id something like this.
when (val viewType = viewModel.viewTypeGallery.get()) {
is GalleryViewModel.GalleryViewType.Gallery -> {
Gallery(viewModel, scope, installId, filePathModifier, fragment, setImageUploadType)
}
is GalleryViewModel.GalleryViewType.ImageViewer -> {
Row(Modifier.fillMaxWidth()) {
Image(
modifier = Modifier
.fillMaxSize(),
painter = rememberCoilPainter(viewType.imgUrl),
contentScale = ContentScale.Crop,
contentDescription = null
)
}
}
}
I just keep track of what type the view is meant to be. In my case I'm not displaying a dialog I'm removing my entire gallery and showing an image instead.
Alternatively you could just have an if(viewImage) condition below your call your and layer the 'dialog' on top of it.
After notice that, at least for now, we don't have any Composable to do "easy" fullscreen, I decided to implement mine one, mostly based on ideas from #foxtrotuniform6969 and #ntoskrnl. Also, I tried to do it most possible without to use platform dependent functions then I think this is very suiteable to Desktop/Android.
You can check the basic implementation in this GitHub repository.
By the way, the implementation idea was just:
Create a composable to wrap the target composables tree that can call an FullScreen composable;
Retrieve the full screen dimensions/size from a auxiliary Box matched to the root screen size using the .onGloballyPositioned() modifier;
Store the full screen size and all FullScreen composables created in the tree onto appropriated compositionLocalOf instances (see documentation).
I tried to use this in a Desktop project and seems to be working, however I didn't tested in Android yet. The repository also contains a example.
Feel free to navigate in the repository and sent a pull request if you can. :)

Categories

Resources