Skip to content

Commit

Permalink
Added ApplicationLifecycle for iOS and tvOS targets
Browse files Browse the repository at this point in the history
  • Loading branch information
arkivanov committed Nov 16, 2023
1 parent 4d8b34e commit aba0059
Show file tree
Hide file tree
Showing 3 changed files with 104 additions and 54 deletions.
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)
}
}

0 comments on commit aba0059

Please sign in to comment.