Skip to content

Commit 3db7f38

Browse files
committed
fix(ios): make UrApiService resolve the active api live, not once at init
The previous fix (.id(deviceManager.activeHostName) on the views that instantiate LoginNavigationView) was unreliable in practice - after applying a custom network in the login flow's NetworkServerSheet, the app kept using the old network for wallet logins until restarted, even though auth-code login picked up the change immediately. Root cause: UrApiService captured a single SdkApi reference in its initializer (private let api: SdkApi) and never re-read it. Forcing SwiftUI to destroy/recreate the whole LoginNavigationView subtree via .id() should have rebuilt this, but proved unreliable - likely because the .sheet(...) that calls deviceManager.applyNetworkSpace(...) is itself hosted by the view being torn down, so the id-triggered recreation can race with or get coalesced away by the sheet's own dismissal transaction. This mirrors Android's actual fix: application.api is a *computed* property () that's re-evaluated on every access, never cached. Ported the same pattern to iOS: - UrApiService.api is now a computed property backed by an closure, resolved on every call instead of once at init. Added used at the top of every async function (mechanically, via ) which throws a clear error if no active api exists, instead of the old compile-time-guaranteed-non-optional but effectively stale reference. - Kept the existing for call sites that don't need to track network changes (e.g. MainView, once fully logged in). - Added for call sites that do. - LoginNavigationView now builds urApiService as a computed property reading live via a weak closure, instead of storing a UrApiService built once in init() (before deviceManager, an @EnvironmentObject, was even available). The .id(deviceManager.activeHostName) modifiers added previously are left in place as defense-in-depth (they still correctly reset transient @State like navigation path and in-flight sheet state on network switch) but are no longer the mechanism relied on for the api itself to update.
1 parent ecde12b commit 3db7f38

2 files changed

Lines changed: 86 additions & 5 deletions

File tree

app/network/Authenticate/LoginNavigationView.swift

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,24 @@ struct LoginNavigationView: View {
1818
@EnvironmentObject var deviceManager: DeviceManager
1919

2020
var api: SdkApi
21-
let urApiService: UrApiServiceProtocol
2221
var cancel: (() -> Void)? = nil
2322
var handleSuccess: (_ jwt: String) async -> Void
23+
24+
// Built from a live closure over `deviceManager.api` (an @EnvironmentObject,
25+
// not available yet at init time) rather than the `api` snapshot passed
26+
// in, so that switching the active network space (Settings > Change
27+
// Network API) while this view is on screen is picked up immediately by
28+
// every subsequent API call - including wallet-auth challenge/login,
29+
// which previously kept hitting the network active when the login flow
30+
// was first presented until the app was restarted.
31+
private var urApiService: UrApiServiceProtocol {
32+
UrApiService(apiProvider: { [weak deviceManager] in
33+
deviceManager?.api ?? api
34+
})
35+
}
2436

2537
init(api: SdkApi, cancel: (() -> Void)? = nil, handleSuccess: @escaping (_ jwt: String) async -> Void) {
2638
self.api = api
27-
self.urApiService = UrApiService(api: api)
2839
self.cancel = cancel
2940
self.handleSuccess = handleSuccess
3041
_guestUpgradeViewModel = StateObject(wrappedValue: GuestUpgradeViewModel(api: api))

app/network/Shared/Services/UrApiService/UrApiService.swift

Lines changed: 73 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,41 @@ import URnetworkSdk
1010

1111
class UrApiService: UrApiServiceProtocol {
1212

13-
private let api: SdkApi
13+
// Resolved live on every call instead of captured once at init. This
14+
// mirrors Android's `application.api` (a `get() = networkSpaceManagerProvider
15+
// .getNetworkSpace()?.api` computed property) so that switching the
16+
// active network space (Settings > Change Network API) is picked up
17+
// immediately by any in-flight or new API call, without needing to
18+
// tear down and recreate the whole view hierarchy that holds this
19+
// service (which proved unreliable via SwiftUI .id() invalidation).
20+
private let apiProvider: () -> SdkApi?
21+
private var api: SdkApi? {
22+
apiProvider()
23+
}
1424

1525
let domain = "UrApiService"
1626

27+
/// Fixed-api initializer, kept for call sites/tests that already have a
28+
/// concrete `SdkApi` and don't need to react to network switches (e.g.
29+
/// once a device is fully initialized and logged in, the api rarely
30+
/// changes for the lifetime of that screen).
1731
init(api: SdkApi) {
18-
self.api = api
32+
self.apiProvider = { api }
33+
}
34+
35+
/// Live-provider initializer. Pass a closure that always resolves the
36+
/// current api (e.g. `{ deviceManager.api }`) so this service tracks
37+
/// network-space changes made while the view using it is still on
38+
/// screen (the login flow, most notably).
39+
init(apiProvider: @escaping () -> SdkApi?) {
40+
self.apiProvider = apiProvider
41+
}
42+
43+
private func requireApi() throws -> SdkApi {
44+
guard let api else {
45+
throw NSError(domain: domain, code: -1, userInfo: [NSLocalizedDescriptionKey: "No active network API available"])
46+
}
47+
return api
1948
}
2049

2150
private func nonEmptyJwt(_ jwt: String, context: String) -> Result<String, Error> {
@@ -37,6 +66,8 @@ extension UrApiService {
3766
func getLeaderboard() async throws -> [LeaderboardEntry] {
3867
let args = SdkGetLeaderboardArgs()
3968

69+
let api = try requireApi()
70+
4071
let result: SdkLeaderboardResult = try await withCheckedThrowingContinuation { continuation in
4172

4273
let callback = GetLeaderboardCallback { result, err in
@@ -59,7 +90,7 @@ extension UrApiService {
5990
continuation.resume(returning: result)
6091
}
6192

62-
self.api.getLeaderboard(args, callback: callback)
93+
api.getLeaderboard(args, callback: callback)
6394
}
6495

6596
var earners: [LeaderboardEntry] = []
@@ -95,6 +126,8 @@ extension UrApiService {
95126
*/
96127
func setNetworkRankingPublic(_ isPublic: Bool) async throws {
97128

129+
let api = try requireApi()
130+
98131
let _: SdkSetNetworkRankingPublicResult = try await withCheckedThrowingContinuation { continuation in
99132

100133
let callback = SetLeaderboardVisibilityCallback { result, err in
@@ -132,6 +165,8 @@ extension UrApiService {
132165
*/
133166
func getLeaderboardRanking() async throws -> SdkGetNetworkRankingResult {
134167

168+
let api = try requireApi()
169+
135170
return try await withCheckedThrowingContinuation { continuation in
136171

137172
let callback = GetNetworkRankingCallback { result, err in
@@ -170,6 +205,7 @@ extension UrApiService {
170205
feedback: String,
171206
starCount: Int
172207
) async throws -> SdkFeedbackSendResult {
208+
let api = try requireApi()
173209
return try await withCheckedThrowingContinuation { continuation in
174210

175211
let callback = SendFeedbackCallback { result, err in
@@ -208,6 +244,7 @@ extension UrApiService {
208244
* Search providers
209245
*/
210246
func searchProviders(_ query: String) async throws -> SdkFilteredLocations {
247+
let api = try requireApi()
211248
return try await withCheckedThrowingContinuation { continuation in
212249

213250
let callback = FindLocationsCallback { result, err in
@@ -239,6 +276,7 @@ extension UrApiService {
239276
* Get all providers
240277
*/
241278
func getAllProviders() async throws -> SdkFilteredLocations {
279+
let api = try requireApi()
242280
return try await withCheckedThrowingContinuation { continuation in
243281

244282
let callback = FindLocationsCallback { result, err in
@@ -270,6 +308,7 @@ extension UrApiService {
270308
extension UrApiService {
271309

272310
func authLogin(_ args: SdkAuthLoginArgs) async throws -> AuthLoginResult {
311+
let api = try requireApi()
273312
return try await withCheckedThrowingContinuation { continuation in
274313

275314
let callback = AuthLoginCallback { result, error in
@@ -357,6 +396,7 @@ extension UrApiService {
357396
}
358397

359398
func createNetwork(_ args: SdkNetworkCreateArgs) async throws -> LoginNetworkResult {
399+
let api = try requireApi()
360400
return try await withCheckedThrowingContinuation { continuation in
361401

362402
let callback = NetworkCreateCallback { result, err in
@@ -402,6 +442,7 @@ extension UrApiService {
402442
}
403443

404444
func upgradeGuest(_ args: SdkUpgradeGuestArgs) async throws -> LoginNetworkResult {
445+
let api = try requireApi()
405446
return try await withCheckedThrowingContinuation { continuation in
406447

407448
let callback = UpgradeGuestCallback { result, err in
@@ -448,6 +489,8 @@ extension UrApiService {
448489

449490
func createAuthCode() async throws -> SdkAuthCodeCreateResult {
450491

492+
let api = try requireApi()
493+
451494
return try await withCheckedThrowingContinuation { continuation in
452495

453496
let callback = AuthCodeCreateCallback { result, err in
@@ -481,6 +524,7 @@ extension UrApiService {
481524
}
482525

483526
func authCodeLogin(_ args: SdkAuthCodeLoginArgs) async throws -> SdkAuthCodeLoginResult {
527+
let api = try requireApi()
484528
return try await withCheckedThrowingContinuation { continuation in
485529

486530
let callback = AuthCodeLoginCallback { result, err in
@@ -516,6 +560,7 @@ extension UrApiService {
516560
}
517561

518562
func authWalletChallenge(_ args: SdkAuthWalletChallengeArgs) async throws -> SdkAuthWalletChallengeResult {
563+
let api = try requireApi()
519564
return try await withCheckedThrowingContinuation { continuation in
520565

521566
let callback = AuthWalletChallengeCallback { result, err in
@@ -550,6 +595,7 @@ extension UrApiService {
550595
extension UrApiService {
551596

552597
func validateReferralCode(_ code: String) async throws -> SdkValidateReferralCodeResult {
598+
let api = try requireApi()
553599
return try await withCheckedThrowingContinuation { continuation in
554600

555601
let callback = ValidateReferralCallback { result, err in
@@ -584,6 +630,8 @@ extension UrApiService {
584630

585631
func fetchSubscriptionBalance() async throws -> SdkSubscriptionBalanceResult {
586632

633+
let api = try requireApi()
634+
587635
return try await withCheckedThrowingContinuation { continuation in
588636

589637
let callback = GetSubscriptionBalanceCallback { result, err in
@@ -609,6 +657,8 @@ extension UrApiService {
609657

610658
func redeemBalanceCode(_ code: String) async throws -> SdkRedeemBalanceCodeResult {
611659

660+
let api = try requireApi()
661+
612662
return try await withCheckedThrowingContinuation { continuation in
613663

614664
let callback = RedeemBalanceCodeCallback { result, err in
@@ -636,6 +686,7 @@ extension UrApiService {
636686
}
637687

638688
func getRedeemedBalanceCodes() async throws -> SdkGetNetworkRedeemedBalanceCodesResult {
689+
let api = try requireApi()
639690
return try await withCheckedThrowingContinuation { continuation in
640691

641692
let callback = GetNetworkRedeemedBalanceCodesCallback { result, err in
@@ -670,6 +721,8 @@ extension UrApiService {
670721

671722
func blockLocation(_ locationId: SdkId) async throws -> SdkNetworkBlockLocationResult {
672723

724+
let api = try requireApi()
725+
673726
return try await withCheckedThrowingContinuation { continuation in
674727

675728
let callback = BlockLocationCallback { result, err in
@@ -698,6 +751,8 @@ extension UrApiService {
698751

699752
func unblockLocation(_ locationId: SdkId) async throws -> SdkNetworkUnblockLocationResult {
700753

754+
let api = try requireApi()
755+
701756
return try await withCheckedThrowingContinuation { continuation in
702757

703758
let callback = UnblockLocationCallback { result, err in
@@ -726,6 +781,8 @@ extension UrApiService {
726781

727782
func getBlockedLocations() async throws -> SdkGetNetworkBlockedLocationsResult {
728783

784+
let api = try requireApi()
785+
729786
return try await withCheckedThrowingContinuation { continuation in
730787

731788
let callback = GetNetworkBlockedLocationsCallback { result, err in
@@ -756,6 +813,8 @@ extension UrApiService {
756813

757814
func deleteAccount() async throws -> SdkNetworkDeleteResult {
758815

816+
let api = try requireApi()
817+
759818
return try await withCheckedThrowingContinuation { continuation in
760819

761820
let callback = NetworkDeleteCallback { result, err in
@@ -782,6 +841,8 @@ extension UrApiService {
782841

783842
func getReferralNetwork() async throws -> SdkGetReferralNetworkResult {
784843

844+
let api = try requireApi()
845+
785846
return try await withCheckedThrowingContinuation { continuation in
786847

787848
let callback = GetNetworkReferralCallback { result, err in
@@ -806,6 +867,8 @@ extension UrApiService {
806867

807868
func setNetworkReferral(_ referralCode: String) async throws -> SdkSetNetworkReferralResult {
808869

870+
let api = try requireApi()
871+
809872
return try await withCheckedThrowingContinuation { continuation in
810873

811874
let callback = UpdateReferralNetworkCallback { result, err in
@@ -833,6 +896,7 @@ extension UrApiService {
833896
}
834897

835898
func unlinkReferralNetwork() async throws -> SdkUnlinkReferralNetworkResult {
899+
let api = try requireApi()
836900
return try await withCheckedThrowingContinuation { continuation in
837901

838902
let callback = UnlinkReferralNetworkCallback { result, err in
@@ -862,6 +926,8 @@ extension UrApiService {
862926

863927
func getNetworkReliability() async throws -> SdkGetNetworkReliabilityResult {
864928

929+
let api = try requireApi()
930+
865931
return try await withCheckedThrowingContinuation { continuation in
866932

867933
let callback = GetNetworkReliabilityCallback { result, err in
@@ -892,6 +958,8 @@ extension UrApiService {
892958

893959
func validateWalletAddress(address: String, chain: String) async throws -> Bool {
894960

961+
let api = try requireApi()
962+
895963
return try await withCheckedThrowingContinuation { continuation in
896964

897965
let callback = ValidateAddressCallback { result, err in
@@ -1160,6 +1228,7 @@ enum UpdateReferralNetworkError: Error {
11601228
extension UrApiService {
11611229

11621230
func getNetworkClients() async throws -> SdkNetworkClientsResult {
1231+
let api = try requireApi()
11631232
return try await withCheckedThrowingContinuation { continuation in
11641233

11651234
let callback = GetNetworkClientsCallback { result, err in
@@ -1183,6 +1252,7 @@ extension UrApiService {
11831252
}
11841253

11851254
func deviceSetName(deviceId: SdkId, deviceName: String) async throws -> Void {
1255+
let api = try requireApi()
11861256
let _: SdkDeviceSetNameResult = try await withCheckedThrowingContinuation { continuation in
11871257

11881258
let args = SdkDeviceSetNameArgs()

0 commit comments

Comments
 (0)