forked from androidx/androidx
-
Notifications
You must be signed in to change notification settings - Fork 74
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Compose as LifecycleOwner
#1198
Merged
Merged
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
3b54183
Move LocalLifecycleOwner to common
MatkovIvan cfbb45b
Add lifecycle dependency to mpp demo
MatkovIvan 8a01245
Initial implementation of iOS bindings to Lifecycle (#1043)
elijah-semyonov 605e874
Provide LocalLifecycleOwner on Desktop
MatkovIvan 3522b73
add initial implementation of Web bindings for Lifecycle
kropp 4c4b1ac
Update API dump
MatkovIvan File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -27,6 +27,7 @@ import androidx.compose.ui.awt.AwtEventListener | |
import androidx.compose.ui.awt.AwtEventListeners | ||
import androidx.compose.ui.awt.OnlyValidPrimaryMouseButtonFilter | ||
import androidx.compose.ui.input.key.KeyEvent | ||
import androidx.compose.ui.platform.LocalLifecycleOwner | ||
import androidx.compose.ui.platform.PlatformContext | ||
import androidx.compose.ui.platform.PlatformWindowContext | ||
import androidx.compose.ui.scene.skia.SkiaLayerComponent | ||
|
@@ -43,12 +44,16 @@ import androidx.compose.ui.window.WindowExceptionHandler | |
import androidx.compose.ui.window.density | ||
import androidx.compose.ui.window.layoutDirectionFor | ||
import androidx.compose.ui.window.sizeInPx | ||
import androidx.lifecycle.Lifecycle | ||
import androidx.lifecycle.LifecycleOwner | ||
import androidx.lifecycle.LifecycleRegistry | ||
import java.awt.Component | ||
import java.awt.Window | ||
import java.awt.event.ComponentEvent | ||
import java.awt.event.ComponentListener | ||
import java.awt.event.WindowEvent | ||
import java.awt.event.WindowFocusListener | ||
import java.awt.event.WindowListener | ||
import javax.swing.JLayeredPane | ||
import javax.swing.SwingUtilities | ||
import kotlin.coroutines.AbstractCoroutineContextElement | ||
|
@@ -80,7 +85,7 @@ internal class ComposeContainer( | |
|
||
private val useSwingGraphics: Boolean = ComposeFeatureFlags.useSwingGraphics, | ||
private val layerType: LayerType = ComposeFeatureFlags.layerType, | ||
) : ComponentListener, WindowFocusListener { | ||
) : ComponentListener, WindowFocusListener, WindowListener, LifecycleOwner { | ||
val windowContext = PlatformWindowContext() | ||
var window: Window? = null | ||
private set | ||
|
@@ -145,16 +150,22 @@ internal class ComposeContainer( | |
val renderApi by mediator::renderApi | ||
val preferredSize by mediator::preferredSize | ||
|
||
override val lifecycle = LifecycleRegistry(this) | ||
|
||
init { | ||
setWindow(window) | ||
this.windowContainer = windowContainer | ||
|
||
if (layerType == LayerType.OnComponent && !useSwingGraphics) { | ||
error("Unsupported LayerType.OnComponent might be used only with rendering to Swing graphics") | ||
} | ||
|
||
lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_CREATE) | ||
} | ||
|
||
fun dispose() { | ||
lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY) | ||
|
||
_windowContainer?.removeComponentListener(this) | ||
mediator.dispose() | ||
layers.fastForEach(DesktopComposeSceneLayer::close) | ||
|
@@ -175,10 +186,27 @@ internal class ComposeContainer( | |
override fun windowGainedFocus(event: WindowEvent) = onChangeWindowFocus() | ||
override fun windowLostFocus(event: WindowEvent) = onChangeWindowFocus() | ||
|
||
override fun windowOpened(e: WindowEvent) = Unit | ||
override fun windowClosing(e: WindowEvent) = Unit | ||
override fun windowClosed(e: WindowEvent) = Unit | ||
override fun windowIconified(e: WindowEvent) = | ||
lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_STOP) | ||
override fun windowDeiconified(e: WindowEvent) = | ||
// The window is always in focus at this moment, so bump the state to [RESUMED]. | ||
// It will generate [ON_START] event implicitly or skip it at all if [windowGainedFocus] | ||
igordmn marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// happened first. | ||
lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_RESUME) | ||
override fun windowActivated(e: WindowEvent) = Unit | ||
override fun windowDeactivated(e: WindowEvent) = Unit | ||
|
||
private fun onChangeWindowFocus() { | ||
windowContext.setWindowFocused(window?.isFocused ?: false) | ||
val isFocused = window?.isFocused ?: false | ||
windowContext.setWindowFocused(isFocused) | ||
mediator.onChangeWindowFocus() | ||
layers.fastForEach(DesktopComposeSceneLayer::onChangeWindowFocus) | ||
lifecycle.handleLifecycleEvent( | ||
event = if (isFocused) Lifecycle.Event.ON_RESUME else Lifecycle.Event.ON_PAUSE | ||
) | ||
} | ||
|
||
private fun onChangeWindowPosition() { | ||
|
@@ -227,9 +255,13 @@ internal class ComposeContainer( | |
// Re-checking the actual size if it wasn't available during init. | ||
onChangeWindowSize() | ||
onChangeWindowPosition() | ||
|
||
lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_START) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We set
So, we have 2 sources of truth and races between them. To avoid that, we should either choose one source of truth, or combine |
||
} | ||
|
||
fun removeNotify() { | ||
lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_STOP) | ||
|
||
setWindow(null) | ||
} | ||
|
||
|
@@ -248,7 +280,9 @@ internal class ComposeContainer( | |
} | ||
|
||
this.window?.removeWindowFocusListener(this) | ||
this.window?.removeWindowListener(this) | ||
window?.addWindowFocusListener(this) | ||
window?.addWindowListener(this) | ||
this.window = window | ||
|
||
onChangeWindowFocus() | ||
|
@@ -441,5 +475,6 @@ private fun ProvideContainerCompositionLocals( | |
composeContainer: ComposeContainer, | ||
content: @Composable () -> Unit, | ||
) = CompositionLocalProvider( | ||
LocalLifecycleOwner provides composeContainer, | ||
content = content | ||
) |
32 changes: 32 additions & 0 deletions
32
compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/platform/CompositionLocals.skiko.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
/* | ||
* Copyright 2024 The Android Open Source Project | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package androidx.compose.ui.platform | ||
|
||
import androidx.compose.runtime.staticCompositionLocalOf | ||
import androidx.lifecycle.LifecycleOwner | ||
|
||
/** | ||
* The CompositionLocal containing the current [LifecycleOwner]. | ||
*/ | ||
actual val LocalLifecycleOwner = staticCompositionLocalOf<LifecycleOwner> { | ||
noLocalProvidedFor("LocalLifecycleOwner") | ||
} | ||
|
||
@Suppress("SameParameterValue") | ||
private fun noLocalProvidedFor(name: String): Nothing { | ||
error("CompositionLocal $name not present") | ||
} |
76 changes: 76 additions & 0 deletions
76
...e/ui/ui/src/uikitMain/kotlin/androidx/compose/ui/window/ApplicationStateListener.uikit.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
/* | ||
* Copyright 2024 The Android Open Source Project | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package androidx.compose.ui.window | ||
|
||
import kotlinx.cinterop.ObjCAction | ||
import platform.Foundation.NSNotificationCenter | ||
import platform.Foundation.NSSelectorFromString | ||
import platform.UIKit.UIApplication | ||
import platform.UIKit.UIApplicationDidEnterBackgroundNotification | ||
import platform.UIKit.UIApplicationState | ||
import platform.UIKit.UIApplicationWillEnterForegroundNotification | ||
import platform.darwin.NSObject | ||
|
||
internal class ApplicationStateListener( | ||
/** | ||
* Callback which will be called with `true` when the app becomes active, and `false` when the app goes background | ||
*/ | ||
private val callback: (Boolean) -> Unit | ||
) : NSObject() { | ||
init { | ||
val notificationCenter = NSNotificationCenter.defaultCenter | ||
|
||
notificationCenter.addObserver( | ||
this, | ||
NSSelectorFromString(::applicationWillEnterForeground.name), | ||
UIApplicationWillEnterForegroundNotification, | ||
null | ||
) | ||
|
||
notificationCenter.addObserver( | ||
this, | ||
NSSelectorFromString(::applicationDidEnterBackground.name), | ||
UIApplicationDidEnterBackgroundNotification, | ||
null | ||
) | ||
} | ||
|
||
@ObjCAction | ||
fun applicationWillEnterForeground() { | ||
callback(true) | ||
} | ||
|
||
@ObjCAction | ||
fun applicationDidEnterBackground() { | ||
callback(false) | ||
} | ||
|
||
/** | ||
* Deregister from [NSNotificationCenter] | ||
*/ | ||
fun dispose() { | ||
val notificationCenter = NSNotificationCenter.defaultCenter | ||
|
||
notificationCenter.removeObserver(this, UIApplicationWillEnterForegroundNotification, null) | ||
notificationCenter.removeObserver(this, UIApplicationDidEnterBackgroundNotification, null) | ||
} | ||
|
||
companion object { | ||
val isApplicationActive: Boolean | ||
get() = UIApplication.sharedApplication.applicationState != UIApplicationState.UIApplicationStateBackground | ||
} | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because we add a new dependency, this change requires 2 approves, please ask someone else to review this commit (about adding dependency).
It looks fine for me, because we either will release a stable version of
lifecycle
, or we just depend on stable API of it right now.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@eymar, no need to review the whole PR, only this commit
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Having these dependencies added, we can't avoid publishing lifecycle libraries (there was an idea that maybe we don't need to publish lifecyclce right now - #1204 (comment)). User projects won't work with this ui library if we don't publish lifecycle.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you talk about my comment:
I meant that "if don't need it right now". But we definitely need it for the 1.6.10 release
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
even dev builds won't work untill lifecycle is published if this PR is merged before that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed. Then let's wait until we publish