Skip to content
This repository was archived by the owner on Feb 24, 2025. It is now read-only.

Commit 8b848d7

Browse files
authored
Refactor app startup code - extracting services (#3859)
Task/Issue URL: https://app.asana.com/0/0/1208832732122404/f **Description**: - Extract service-related code from the app’s launching logic to improve maintainability and clarity. - Establish clear patterns for synchronizing crucial services, ensuring consistent behavior during app lifecycle events. - Design an intuitive API to simplify usage for developers interacting with the app lifecycle code.
1 parent e07d032 commit 8b848d7

File tree

62 files changed

+2657
-2016
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+2657
-2016
lines changed

Core/PixelEvent.swift

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -962,7 +962,6 @@ extension Pixel {
962962
// MARK: Launch time
963963
case appDidFinishLaunchingTime(time: BucketAggregation)
964964
case appDidShowUITime(time: BucketAggregation)
965-
case appDidBecomeActiveTime(time: BucketAggregation)
966965

967966
// MARK: AI Chat
968967
case aiChatNoRemoteSettingsFound(settings: String)
@@ -1951,7 +1950,6 @@ extension Pixel.Event {
19511950
// MARK: Launch time
19521951
case .appDidFinishLaunchingTime(let time): return "m_debug_app-did-finish-launching-time-\(time)"
19531952
case .appDidShowUITime(let time): return "m_debug_app-did-show-ui-time-\(time)"
1954-
case .appDidBecomeActiveTime(let time): return "m_debug_app-did-become-active-time-\(time)"
19551953

19561954
// MARK: AI Chat
19571955
case .aiChatNoRemoteSettingsFound(let settings):
@@ -1963,7 +1961,7 @@ extension Pixel.Event {
19631961
case .openAIChatFromWidgetLockScreenComplication: return "m_aichat-widget-lock-screen-complication"
19641962

19651963
// MARK: Lifecycle
1966-
case .appDidTransitionToUnexpectedState: return "m_debug_app-did-transition-to-unexpected-state-3"
1964+
case .appDidTransitionToUnexpectedState: return "m_debug_app-did-transition-to-unexpected-state-4"
19671965

19681966
case .debugBreakageExperiment: return "m_debug_breakage_experiment_u"
19691967

Core/StatisticsLoader.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@ public class StatisticsLoader {
5454
}
5555

5656
public func load(completion: @escaping Completion = {}) {
57+
let completion = {
58+
self.refreshAppRetentionAtb()
59+
completion()
60+
}
5761
if statisticsStore.hasInstallStatistics {
5862
completion()
5963
return

Core/UserDefaultsPropertyWrapper.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,6 @@ public struct UserDefaultsWrapper<T> {
5757
case daxLastShownContextualOnboardingDialogType = "com.duckduckgo.ios.daxLastShownContextualOnboardingDialogType"
5858

5959
case notFoundCache = "com.duckduckgo.ios.favicons.notFoundCache"
60-
case faviconSizeNeedsMigration = "com.duckduckgo.ios.favicons.sizeNeedsMigration"
6160
case faviconTabsCacheNeedsCleanup = "com.duckduckgo.ios.favicons.tabsCacheNeedsCleanup"
6261

6362
case legacyCovidInfo = "com.duckduckgo.ios.home.covidInfo"

DuckDuckGo-iOS.xcodeproj/project.pbxproj

Lines changed: 128 additions & 35 deletions
Large diffs are not rendered by default.
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
//
2+
// ATBAndVariantConfiguration.swift
3+
// DuckDuckGo
4+
//
5+
// Copyright © 2025 DuckDuckGo. All rights reserved.
6+
//
7+
// Licensed under the Apache License, Version 2.0 (the "License");
8+
// you may not use this file except in compliance with the License.
9+
// You may obtain a copy of the License at
10+
//
11+
// http://www.apache.org/licenses/LICENSE-2.0
12+
//
13+
// Unless required by applicable law or agreed to in writing, software
14+
// distributed under the License is distributed on an "AS IS" BASIS,
15+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
// See the License for the specific language governing permissions and
17+
// limitations under the License.
18+
//
19+
20+
import Foundation
21+
import Core
22+
import BrowserServicesKit
23+
24+
final class ATBAndVariantConfiguration {
25+
26+
lazy var variantManager = DefaultVariantManager()
27+
28+
func configure(onVariantAssigned: () -> Void) {
29+
cleanUpATBAndAssignVariant(onVariantAssigned: onVariantAssigned)
30+
}
31+
32+
private func cleanUpATBAndAssignVariant(onVariantAssigned: () -> Void) {
33+
AtbAndVariantCleanup.cleanup()
34+
variantManager.assignVariantIfNeeded { _ in
35+
onVariantAssigned()
36+
}
37+
}
38+
39+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
//
2+
// ContentBlockingConfiguration.swift
3+
// DuckDuckGo
4+
//
5+
// Copyright © 2025 DuckDuckGo. All rights reserved.
6+
//
7+
// Licensed under the Apache License, Version 2.0 (the "License");
8+
// you may not use this file except in compliance with the License.
9+
// You may obtain a copy of the License at
10+
//
11+
// http://www.apache.org/licenses/LICENSE-2.0
12+
//
13+
// Unless required by applicable law or agreed to in writing, software
14+
// distributed under the License is distributed on an "AS IS" BASIS,
15+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
// See the License for the specific language governing permissions and
17+
// limitations under the License.
18+
//
19+
20+
import Foundation
21+
import Core
22+
23+
final class ContentBlockingConfiguration {
24+
25+
static func configure() {
26+
ContentBlocking.shared.onCriticalError = {
27+
NotificationCenter.default.post(name: .appDidEncounterUnrecoverableState, object: nil)
28+
}
29+
// Explicitly prepare ContentBlockingUpdating instance before Tabs are created
30+
_ = ContentBlockingUpdating.shared
31+
}
32+
33+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
//
2+
// CrashHandlersConfiguration.swift
3+
// DuckDuckGo
4+
//
5+
// Copyright © 2025 DuckDuckGo. All rights reserved.
6+
//
7+
// Licensed under the Apache License, Version 2.0 (the "License");
8+
// you may not use this file except in compliance with the License.
9+
// You may obtain a copy of the License at
10+
//
11+
// http://www.apache.org/licenses/LICENSE-2.0
12+
//
13+
// Unless required by applicable law or agreed to in writing, software
14+
// distributed under the License is distributed on an "AS IS" BASIS,
15+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
// See the License for the specific language governing permissions and
17+
// limitations under the License.
18+
//
19+
20+
import Foundation
21+
import Core
22+
import Crashes
23+
24+
final class CrashHandlersConfiguration {
25+
26+
@UserDefaultsWrapper(key: .didCrashDuringCrashHandlersSetUp, defaultValue: false)
27+
static var didCrashDuringCrashHandlersSetUp: Bool
28+
29+
static func setupCrashHandlers() {
30+
if !didCrashDuringCrashHandlersSetUp {
31+
didCrashDuringCrashHandlersSetUp = true
32+
CrashLogMessageExtractor.setUp(swapCxaThrow: false)
33+
didCrashDuringCrashHandlersSetUp = false
34+
}
35+
}
36+
37+
static func handleCrashDuringCrashHandlersSetup() {
38+
if didCrashDuringCrashHandlersSetUp {
39+
Pixel.fire(pixel: .crashOnCrashHandlersSetUp)
40+
didCrashDuringCrashHandlersSetUp = false
41+
}
42+
}
43+
44+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
//
2+
// HistoryManagerConfiguration.swift
3+
// DuckDuckGo
4+
//
5+
// Copyright © 2025 DuckDuckGo. All rights reserved.
6+
//
7+
// Licensed under the Apache License, Version 2.0 (the "License");
8+
// you may not use this file except in compliance with the License.
9+
// You may obtain a copy of the License at
10+
//
11+
// http://www.apache.org/licenses/LICENSE-2.0
12+
//
13+
// Unless required by applicable law or agreed to in writing, software
14+
// distributed under the License is distributed on an "AS IS" BASIS,
15+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
// See the License for the specific language governing permissions and
17+
// limitations under the License.
18+
//
19+
20+
import Foundation
21+
22+
final class HistoryManagerConfiguration {
23+
24+
func onVariantAssigned() {
25+
// New users don't see the message
26+
HistoryMessageManager().dismiss()
27+
}
28+
29+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
//
2+
// KeyboardConfiguration.swift
3+
// DuckDuckGo
4+
//
5+
// Copyright © 2025 DuckDuckGo. All rights reserved.
6+
//
7+
// Licensed under the Apache License, Version 2.0 (the "License");
8+
// you may not use this file except in compliance with the License.
9+
// You may obtain a copy of the License at
10+
//
11+
// http://www.apache.org/licenses/LICENSE-2.0
12+
//
13+
// Unless required by applicable law or agreed to in writing, software
14+
// distributed under the License is distributed on an "AS IS" BASIS,
15+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
// See the License for the specific language governing permissions and
17+
// limitations under the License.
18+
//
19+
20+
import UIKit
21+
22+
struct KeyboardConfiguration {
23+
24+
static func configure() {
25+
#if targetEnvironment(simulator)
26+
if ProcessInfo.processInfo.environment["UITESTING"] == "true" {
27+
// Disable hardware keyboards.
28+
let setHardwareLayout = NSSelectorFromString("setHardwareLayout:")
29+
UITextInputMode.activeInputModes
30+
// Filter `UIKeyboardInputMode`s.
31+
.filter({ $0.responds(to: setHardwareLayout) })
32+
.forEach { $0.perform(setHardwareLayout, with: nil) }
33+
}
34+
#endif
35+
}
36+
37+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
//
2+
// OnboardingConfiguration.swift
3+
// DuckDuckGo
4+
//
5+
// Copyright © 2025 DuckDuckGo. All rights reserved.
6+
//
7+
// Licensed under the Apache License, Version 2.0 (the "License");
8+
// you may not use this file except in compliance with the License.
9+
// You may obtain a copy of the License at
10+
//
11+
// http://www.apache.org/licenses/LICENSE-2.0
12+
//
13+
// Unless required by applicable law or agreed to in writing, software
14+
// distributed under the License is distributed on an "AS IS" BASIS,
15+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
// See the License for the specific language governing permissions and
17+
// limitations under the License.
18+
//
19+
20+
import Foundation
21+
import Core
22+
23+
final class OnboardingConfiguration {
24+
25+
lazy var daxDialogs = DaxDialogs.shared
26+
27+
func migrate() {
28+
// Hide Dax Dialogs if users already completed old onboarding.
29+
DaxDialogsOnboardingMigrator().migrateFromOldToNewOboarding()
30+
}
31+
32+
// assign it here, because "did become active" is already too late and "viewWillAppear"
33+
// has already been called on the HomeViewController so won't show the home row CTA
34+
func onVariantAssigned() {
35+
let launchOptionsHandler = LaunchOptionsHandler()
36+
37+
// MARK: perform first time launch logic here
38+
// If it's running UI Tests check if the onboarding should be in a completed state.
39+
if launchOptionsHandler.isUITesting && launchOptionsHandler.isOnboardingCompleted {
40+
daxDialogs.dismiss()
41+
} else {
42+
daxDialogs.primeForUse()
43+
}
44+
}
45+
46+
}

0 commit comments

Comments
 (0)