Skip to content
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

Added ApplicationLifecycle for iOS and tvOS targets #527

Merged
merged 1 commit into from
Nov 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion decompose/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,16 @@ kotlin {
val nonAndroid by bundle()
val nonNative by bundle()
val darwin by bundle()
val itvos by bundle()
val js by bundle()
val nonJs by bundle()

(nonAndroid + darwin + nonNative + nonJs) dependsOn common
(allSet - android) dependsOn nonAndroid
(allSet - nativeSet) dependsOn nonNative
(allSet - js) dependsOn nonJs
darwinSet dependsOn darwin
(iosSet + tvosSet) dependsOn itvos
(darwinSet - iosSet - tvosSet + itvos) dependsOn darwin

all {
languageSettings {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package com.arkivanov.decompose.lifecycle

import com.arkivanov.decompose.ExperimentalDecomposeApi
import com.arkivanov.essenty.lifecycle.Lifecycle
import com.arkivanov.essenty.lifecycle.LifecycleRegistry
import com.arkivanov.essenty.lifecycle.destroy
import com.arkivanov.essenty.lifecycle.pause
import com.arkivanov.essenty.lifecycle.resume
import com.arkivanov.essenty.lifecycle.start
import com.arkivanov.essenty.lifecycle.stop
import kotlinx.cinterop.BetaInteropApi
import kotlinx.cinterop.ExperimentalForeignApi
import kotlinx.cinterop.ObjCAction
import platform.Foundation.NSNotificationCenter
import platform.Foundation.NSNotificationName
import platform.Foundation.NSSelectorFromString
import platform.UIKit.UIApplicationDidBecomeActiveNotification
import platform.UIKit.UIApplicationDidEnterBackgroundNotification
import platform.UIKit.UIApplicationWillEnterForegroundNotification
import platform.UIKit.UIApplicationWillResignActiveNotification
import platform.UIKit.UIApplicationWillTerminateNotification

/**
* An implementation of [Lifecycle] that follows the [UIApplication][platform.UIKit.UIApplication] lifecycle notifications.
*/
@ExperimentalDecomposeApi
class ApplicationLifecycle private constructor(
private val lifecycle: LifecycleRegistry,
) : Lifecycle by lifecycle {

constructor() : this(lifecycle = LifecycleRegistry())

init {
addObserver(name = UIApplicationWillEnterForegroundNotification, selectorName = "willEnterForeground")
addObserver(name = UIApplicationDidBecomeActiveNotification, selectorName = "didBecomeActive")
addObserver(name = UIApplicationWillResignActiveNotification, selectorName = "willResignActive")
addObserver(name = UIApplicationDidEnterBackgroundNotification, selectorName = "didEnterBackground")
addObserver(name = UIApplicationWillTerminateNotification, selectorName = "willTerminate")
}

@OptIn(ExperimentalForeignApi::class)
private fun addObserver(name: NSNotificationName, selectorName: String) {
NSNotificationCenter.defaultCenter.addObserver(
name = name,
`object` = null,
observer = this,
selector = NSSelectorFromString(selectorName),
)
}

@Suppress("unused")
@OptIn(BetaInteropApi::class)
@ObjCAction
fun willEnterForeground() {
lifecycle.start()
}

@Suppress("unused")
@OptIn(BetaInteropApi::class)
@ObjCAction
fun didBecomeActive() {
lifecycle.resume()
}

@Suppress("unused")
@OptIn(BetaInteropApi::class)
@ObjCAction
fun willResignActive() {
lifecycle.pause()
}

@Suppress("unused")
@OptIn(BetaInteropApi::class)
@ObjCAction
fun didEnterBackground() {
lifecycle.stop()
}

@OptIn(BetaInteropApi::class)
@ObjCAction
fun willTerminate() {
lifecycle.destroy()
}
}
70 changes: 17 additions & 53 deletions sample/app-ios/app-ios/app_iosApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,76 +13,40 @@ struct app_iosApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self)
var appDelegate: AppDelegate

private var rootHolder: RootHolder { appDelegate.getRootHolder() }

var body: some Scene {
WindowGroup {
RootView(rootHolder.root)
.onReceive(NotificationCenter.default.publisher(for: UIApplication.didBecomeActiveNotification)) { _ in
LifecycleRegistryExtKt.resume(rootHolder.lifecycle)
}
.onReceive(NotificationCenter.default.publisher(for: UIApplication.willResignActiveNotification)) { _ in
LifecycleRegistryExtKt.pause(rootHolder.lifecycle)
}
.onReceive(NotificationCenter.default.publisher(for: UIApplication.didEnterBackgroundNotification)) { _ in
LifecycleRegistryExtKt.stop(rootHolder.lifecycle)
}
.onReceive(NotificationCenter.default.publisher(for: UIApplication.willTerminateNotification)) { _ in
LifecycleRegistryExtKt.destroy(rootHolder.lifecycle)
}
RootView(appDelegate.root)
}
}
}

class AppDelegate: NSObject, UIApplicationDelegate {
private var rootHolder: RootHolder?

private var stateKeeper = StateKeeperDispatcherKt.StateKeeperDispatcher(savedState: nil)

lazy var root: RootComponent = DefaultRootComponent(
componentContext: DefaultComponentContext(
lifecycle: ApplicationLifecycle(),
stateKeeper: stateKeeper,
instanceKeeper: nil,
backHandler: nil
),
featureInstaller: DefaultFeatureInstaller.shared,
deepLink: DefaultRootComponentDeepLinkNone.shared,
webHistoryController: nil
)

func application(_ application: UIApplication, shouldSaveSecureApplicationState coder: NSCoder) -> Bool {
let savedState = rootHolder!.stateKeeper.save()
CodingKt.encodeParcelable(coder, value: savedState, key: "savedState")
CodingKt.encodeParcelable(coder, value: stateKeeper.save(), key: "savedState")
return true
}

func application(_ application: UIApplication, shouldRestoreSecureApplicationState coder: NSCoder) -> Bool {
do {
let savedState = try CodingKt.decodeParcelable(coder, key: "savedState") as! ParcelableParcelableContainer
rootHolder = RootHolder(savedState: savedState)
stateKeeper = StateKeeperDispatcherKt.StateKeeperDispatcher(savedState: savedState)
return true
} catch {
return false
}
}

fileprivate func getRootHolder() -> RootHolder {
if (rootHolder == nil) {
rootHolder = RootHolder(savedState: nil)
}

return rootHolder!
}
}

private class RootHolder {
let lifecycle: LifecycleRegistry
let stateKeeper: StateKeeperDispatcher
let root: RootComponent

init(savedState: ParcelableParcelableContainer?) {
lifecycle = LifecycleRegistryKt.LifecycleRegistry()
stateKeeper = StateKeeperDispatcherKt.StateKeeperDispatcher(savedState: savedState)

root = DefaultRootComponent(
componentContext: DefaultComponentContext(
lifecycle: lifecycle,
stateKeeper: stateKeeper,
instanceKeeper: nil,
backHandler: nil
),
featureInstaller: DefaultFeatureInstaller.shared,
deepLink: DefaultRootComponentDeepLinkNone.shared,
webHistoryController: nil
)

LifecycleRegistryExtKt.create(lifecycle)
}
}