Skip to content

Commit

Permalink
feat(ConfigUseCase): Supporting shared modules for reading dynamic co…
Browse files Browse the repository at this point in the history
…nfig (#299)

* feat(ConfigUseCase): Supporting shared modules for reading dynamic config

* address feedback
  • Loading branch information
KaylaBrady authored Jul 22, 2024
1 parent bda4a4b commit 4ff11a2
Show file tree
Hide file tree
Showing 18 changed files with 348 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.mbta.tid.mbta_app.android
import android.app.Application
import com.mbta.tid.mbta_app.AppVariant
import com.mbta.tid.mbta_app.android.phoenix.wrapped
import com.mbta.tid.mbta_app.android.repositories.AppCheckRepository
import com.mbta.tid.mbta_app.android.util.decodeMessage
import com.mbta.tid.mbta_app.dependencyInjection.makeNativeModule
import com.mbta.tid.mbta_app.initKoin
Expand All @@ -12,9 +13,10 @@ val appVariant = AppVariant.Prod

class MainApplication : Application() {
private val socket = Socket(appVariant.socketUrl, decode = ::decodeMessage)
private val appCheck = AppCheckRepository()

override fun onCreate() {
super.onCreate()
initKoin(appVariant, makeNativeModule(socket.wrapped()))
initKoin(appVariant, makeNativeModule(appCheck, socket.wrapped()))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.mbta.tid.mbta_app.android.repositories

import com.mbta.tid.mbta_app.model.response.ApiResult
import com.mbta.tid.mbta_app.model.response.ErrorDetails
import com.mbta.tid.mbta_app.repositories.IAppCheckRepository
import com.mbta.tid.mbta_app.repositories.Token

class AppCheckRepository : IAppCheckRepository {
override suspend fun getToken(): ApiResult<Token> {
return ApiResult.Error(ErrorDetails(message = "TODO: Implement"))
}
}
4 changes: 4 additions & 0 deletions iosApp/iosApp.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
6E35D4D02B72C7B700A2BF95 /* HomeMapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E35D4CF2B72C7B700A2BF95 /* HomeMapView.swift */; };
6E35D4D32B72CD3900A2BF95 /* HomeMapViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E35D4D22B72CD3900A2BF95 /* HomeMapViewTests.swift */; };
6E3C8D7E2C11FDA80059C28C /* CloseButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E3C8D7D2C11FDA80059C28C /* CloseButton.swift */; };
6E4C37602C4E94D200EA67CF /* AppCheckRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E4C375F2C4E94D200EA67CF /* AppCheckRepository.swift */; };
6E4EACFC2B7A82AC0011AB8B /* MockLocationFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E4EACFB2B7A82AC0011AB8B /* MockLocationFetcher.swift */; };
6E55C17F2C247D480086A424 /* StopDetailsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E55C17E2C247D480086A424 /* StopDetailsView.swift */; };
6E55C1822C2489030086A424 /* Collections in Frameworks */ = {isa = PBXBuildFile; productRef = 6E55C1812C2489030086A424 /* Collections */; };
Expand Down Expand Up @@ -228,6 +229,7 @@
6E35D4CF2B72C7B700A2BF95 /* HomeMapView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeMapView.swift; sourceTree = "<group>"; };
6E35D4D22B72CD3900A2BF95 /* HomeMapViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeMapViewTests.swift; sourceTree = "<group>"; };
6E3C8D7D2C11FDA80059C28C /* CloseButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloseButton.swift; sourceTree = "<group>"; };
6E4C375F2C4E94D200EA67CF /* AppCheckRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppCheckRepository.swift; sourceTree = "<group>"; };
6E4EACFB2B7A82AC0011AB8B /* MockLocationFetcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockLocationFetcher.swift; sourceTree = "<group>"; };
6E55C17E2C247D480086A424 /* StopDetailsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StopDetailsView.swift; sourceTree = "<group>"; };
6E55C1852C249EF20086A424 /* StopDetailsViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StopDetailsViewTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -870,6 +872,7 @@
ED24EADB2C1A95A900A7BE4D /* StopDetailsAnalytics.swift */,
ED24EADD2C1A986900A7BE4D /* AnalyticsProvider.swift */,
EDE92FA52C3DD675007AD2F6 /* ScreenTracker.swift */,
6E4C375F2C4E94D200EA67CF /* AppCheckRepository.swift */,
);
path = Analytics;
sourceTree = "<group>";
Expand Down Expand Up @@ -1244,6 +1247,7 @@
8CE014102BBDB8DC00918FAE /* StopDetailsRoutesView.swift in Sources */,
9A8B34AD2B88E5090018412C /* RailRouteShapeFetcher.swift in Sources */,
8CB28DAE2C29DCBF0036258E /* ChildStopLayerGenerator.swift in Sources */,
6E4C37602C4E94D200EA67CF /* AppCheckRepository.swift in Sources */,
9AB446B02BBDDCAF00D8C920 /* StopIcons.swift in Sources */,
6EA231712C21BF8900789173 /* RoundedBorderModifier.swift in Sources */,
9A2005CB2B97B68700F562E1 /* UpcomingTripView.swift in Sources */,
Expand Down
17 changes: 17 additions & 0 deletions iosApp/iosApp/Analytics/AppCheckProviderFactory.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//
// AppCheckProviderFactory.swift
// iosApp
//
// Created by Brady, Kayla on 7/12/24.
// Copyright © 2024 MBTA. All rights reserved.
//

import FirebaseAppCheck
import FirebaseCore
import Foundation

class CustomAppCheckProviderFactory: NSObject, AppCheckProviderFactory {
func createProvider(with app: FirebaseApp) -> AppCheckProvider? {
AppAttestProvider(app: app)
}
}
25 changes: 25 additions & 0 deletions iosApp/iosApp/Analytics/AppCheckRepository.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//
// AppCheckRepository.swift
// iosApp
//
// Created by Brady, Kayla on 7/18/24.
// Copyright © 2024 MBTA. All rights reserved.
//

// import FirebaseAppCheck
import Foundation
import shared

class AppCheckRepository: IAppCheckRepository {
// swiftlint:disable:next identifier_name
func __getToken() async -> ApiResult<shared.Token> {
/* do {
let token = try await AppCheck.appCheck().token(forcingRefresh: false)
return ApiResultOk(data: Token(token: token.token))
} catch {
return ApiResultError(error: ErrorDetails(code: nil, message: "\(error)"))
}
*/
ApiResultError(error: ErrorDetails(code: nil, message: "TODO"))
}
}
6 changes: 6 additions & 0 deletions iosApp/iosApp/Pages/Settings/Setting+Convenience.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ extension Setting: Identifiable {
"Search"
case .map:
"Map Debug"
case .dynamicMapKey:
"Dynamic map API Key"
}
}

Expand All @@ -29,6 +31,8 @@ extension Setting: Identifiable {
"magnifyingglass"
case .map:
"location.magnifyingglass"
case .dynamicMapKey:
""
}
}

Expand All @@ -38,6 +42,8 @@ extension Setting: Identifiable {
.featureFlags
case .map:
.debug
case .dynamicMapKey:
.featureFlags
}
}
}
6 changes: 3 additions & 3 deletions iosApp/iosApp/ProductionAppView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ struct ProductionAppView: View {
init() {
Self.initSentry()
let socket = Self.initSocket()
Self.initKoin(socket: socket)
Self.initKoin(appCheck: AppCheckRepository(), socket: socket)
self.init(socket: socket)
}

Expand Down Expand Up @@ -77,8 +77,8 @@ struct ProductionAppView: View {
return socket
}

private static func initKoin(socket: PhoenixSocket) {
let nativeModule: Koin_coreModule = MakeNativeModuleKt.makeNativeModule(socket: socket)
private static func initKoin(appCheck: IAppCheckRepository, socket: PhoenixSocket) {
let nativeModule: Koin_coreModule = MakeNativeModuleKt.makeNativeModule(appCheck: appCheck, socket: socket)
HelpersKt.doInitKoin(appVariant: appVariant, nativeModule: nativeModule)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import com.mbta.tid.mbta_app.AppVariant
import com.mbta.tid.mbta_app.cache.GlobalCache
import com.mbta.tid.mbta_app.network.MobileBackendClient
import com.mbta.tid.mbta_app.repositories.IAlertsRepository
import com.mbta.tid.mbta_app.repositories.IAppCheckRepository
import com.mbta.tid.mbta_app.repositories.IConfigRepository
import com.mbta.tid.mbta_app.repositories.IGlobalRepository
import com.mbta.tid.mbta_app.repositories.INearbyRepository
import com.mbta.tid.mbta_app.repositories.IPinnedRoutesRepository
Expand All @@ -16,6 +18,7 @@ import com.mbta.tid.mbta_app.repositories.IStopRepository
import com.mbta.tid.mbta_app.repositories.ITripPredictionsRepository
import com.mbta.tid.mbta_app.repositories.ITripRepository
import com.mbta.tid.mbta_app.repositories.IVehicleRepository
import com.mbta.tid.mbta_app.usecases.ConfigUseCase
import com.mbta.tid.mbta_app.usecases.GetSettingUsecase
import com.mbta.tid.mbta_app.usecases.TogglePinnedRouteUsecase
import org.koin.core.module.Module
Expand All @@ -34,6 +37,8 @@ fun cacheModule() = module { single { GlobalCache() } }

fun repositoriesModule(repositories: IRepositories): Module {
return module {
repositories.appCheck?.let { appCheckRepo -> factory<IAppCheckRepository> { appCheckRepo } }
single<IConfigRepository> { repositories.config }
single<IPinnedRoutesRepository> { repositories.pinnedRoutes }
single<ISchedulesRepository> { repositories.schedules }
single<ISettingsRepository> { repositories.settings }
Expand All @@ -51,5 +56,6 @@ fun repositoriesModule(repositories: IRepositories): Module {
single<IGlobalRepository> { repositories.global }
single { TogglePinnedRouteUsecase(get()) }
single { GetSettingUsecase(get()) }
single { ConfigUseCase(get(), get()) }
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import co.touchlab.skie.configuration.annotations.DefaultArgumentInterop
import com.mbta.tid.mbta_app.repositories.ConfigRepository
import com.mbta.tid.mbta_app.repositories.GlobalRepository
import com.mbta.tid.mbta_app.repositories.IAlertsRepository
import com.mbta.tid.mbta_app.repositories.IAppCheckRepository
import com.mbta.tid.mbta_app.repositories.IConfigRepository
import com.mbta.tid.mbta_app.repositories.IGlobalRepository
import com.mbta.tid.mbta_app.repositories.INearbyRepository
import com.mbta.tid.mbta_app.repositories.IPinnedRoutesRepository
Expand All @@ -17,6 +20,8 @@ import com.mbta.tid.mbta_app.repositories.IdleScheduleRepository
import com.mbta.tid.mbta_app.repositories.IdleStopRepository
import com.mbta.tid.mbta_app.repositories.IdleTripRepository
import com.mbta.tid.mbta_app.repositories.MockAlertsRepository
import com.mbta.tid.mbta_app.repositories.MockAppCheckRepository
import com.mbta.tid.mbta_app.repositories.MockConfigRepository
import com.mbta.tid.mbta_app.repositories.MockPredictionsRepository
import com.mbta.tid.mbta_app.repositories.MockTripPredictionsRepository
import com.mbta.tid.mbta_app.repositories.MockVehicleRepository
Expand All @@ -30,6 +35,8 @@ import org.koin.core.component.KoinComponent
import org.koin.core.component.inject

interface IRepositories {
val appCheck: IAppCheckRepository?
val config: IConfigRepository
val pinnedRoutes: IPinnedRoutesRepository
val schedules: ISchedulesRepository
val settings: ISettingsRepository
Expand All @@ -44,6 +51,8 @@ interface IRepositories {
}

class RepositoryDI : IRepositories, KoinComponent {
override val appCheck: IAppCheckRepository by inject()
override val config: IConfigRepository by inject()
override val pinnedRoutes: IPinnedRoutesRepository by inject()
override val schedules: ISchedulesRepository by inject()
override val settings: ISettingsRepository by inject()
Expand All @@ -58,6 +67,10 @@ class RepositoryDI : IRepositories, KoinComponent {
}

class RealRepositories : IRepositories {
// initialize repositories with platform-specific dependencies as null.
// instantiate the real repositories in makeNativeModule
override val appCheck = null
override val config = ConfigRepository()
override val pinnedRoutes = PinnedRoutesRepository()
override val schedules = SchedulesRepository()
override val settings = SettingsRepository()
Expand All @@ -72,6 +85,8 @@ class RealRepositories : IRepositories {
}

class MockRepositories(
override val appCheck: IAppCheckRepository,
override val config: IConfigRepository,
override val pinnedRoutes: IPinnedRoutesRepository,
override val schedules: ISchedulesRepository,
override val settings: ISettingsRepository,
Expand All @@ -88,6 +103,7 @@ class MockRepositories(
@DefaultArgumentInterop.Enabled
@DefaultArgumentInterop.MaximumDefaultArgumentCount(99)
fun buildWithDefaults(
configRepository: IConfigRepository = MockConfigRepository(),
pinnedRoutes: IPinnedRoutesRepository = PinnedRoutesRepository(),
schedules: ISchedulesRepository = IdleScheduleRepository(),
settings: ISettingsRepository = SettingsRepository(),
Expand All @@ -101,6 +117,8 @@ class MockRepositories(
global: IGlobalRepository = IdleGlobalRepository()
): MockRepositories {
return MockRepositories(
appCheck = MockAppCheckRepository(),
config = configRepository,
pinnedRoutes = pinnedRoutes,
schedules = schedules,
settings = settings,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package com.mbta.tid.mbta_app.dependencyInjection

import com.mbta.tid.mbta_app.usecases.ConfigUseCase
import com.mbta.tid.mbta_app.usecases.GetSettingUsecase
import com.mbta.tid.mbta_app.usecases.TogglePinnedRouteUsecase
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject

class UsecaseDI() : KoinComponent {
val configUsecase: ConfigUseCase by inject()
val toggledPinnedRouteUsecase: TogglePinnedRouteUsecase by inject()
val getSettingUsecase: GetSettingUsecase by inject()
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.mbta.tid.mbta_app.dependencyInjection
import com.mbta.tid.mbta_app.network.PhoenixSocket
import com.mbta.tid.mbta_app.repositories.AlertsRepository
import com.mbta.tid.mbta_app.repositories.IAlertsRepository
import com.mbta.tid.mbta_app.repositories.IAppCheckRepository
import com.mbta.tid.mbta_app.repositories.IPredictionsRepository
import com.mbta.tid.mbta_app.repositories.ITripPredictionsRepository
import com.mbta.tid.mbta_app.repositories.IVehicleRepository
Expand All @@ -12,8 +13,9 @@ import com.mbta.tid.mbta_app.repositories.VehicleRepository
import org.koin.core.module.Module
import org.koin.dsl.module

fun makeNativeModule(socket: PhoenixSocket): Module {
fun makeNativeModule(appCheck: IAppCheckRepository, socket: PhoenixSocket): Module {
return module {
single<IAppCheckRepository> { appCheck }
single<PhoenixSocket> { socket }
factory<IPredictionsRepository> { PredictionsRepository(get()) }
factory<IAlertsRepository> { AlertsRepository(get()) }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.mbta.tid.mbta_app.model.response

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class ConfigResponse(@SerialName("mapbox_public_token") val mapboxPublicToken: String)
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.mbta.tid.mbta_app.repositories

import com.mbta.tid.mbta_app.model.response.ApiResult
import kotlinx.serialization.Serializable

@Serializable data class Token(var token: String)

interface IAppCheckRepository {
suspend fun getToken(): ApiResult<Token>
}

class MockAppCheckRepository(result: ApiResult<Token>) : IAppCheckRepository {
private var result = result

override suspend fun getToken(): ApiResult<Token> {
return result
}

constructor() : this(ApiResult.Ok(Token("fake_app_check_token")))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package com.mbta.tid.mbta_app.repositories

import com.mbta.tid.mbta_app.json
import com.mbta.tid.mbta_app.model.response.ApiResult
import com.mbta.tid.mbta_app.model.response.ConfigResponse
import com.mbta.tid.mbta_app.model.response.ErrorDetails
import com.mbta.tid.mbta_app.network.MobileBackendClient
import io.ktor.client.call.body
import io.ktor.client.request.header
import io.ktor.client.statement.bodyAsText
import io.ktor.http.HttpStatusCode
import io.ktor.http.path
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject

interface IConfigRepository {

suspend fun getConfig(token: String): ApiResult<ConfigResponse>
}

class ConfigRepository() : IConfigRepository, KoinComponent {
private val mobileBackendClient: MobileBackendClient by inject()

override suspend fun getConfig(token: String): ApiResult<ConfigResponse> {
try {
val response =
mobileBackendClient.get {
url {
path("api/protected/config")
header("http_x_firebase_appcheck", token)
}
}

if (response.status === HttpStatusCode.OK) {
return ApiResult.Ok(data = json.decodeFromString(response.body()))
} else {
return ApiResult.Error(ErrorDetails(response.status.value, response.bodyAsText()))
}
} catch (e: Exception) {
return ApiResult.Error(ErrorDetails(message = e.message ?: e.toString()))
}
}
}

class MockConfigRepository(
var response: ApiResult<ConfigResponse> =
ApiResult.Ok(ConfigResponse(mapboxPublicToken = "fake_mapbox_token"))
) : IConfigRepository, KoinComponent {

override suspend fun getConfig(token: String): ApiResult<ConfigResponse> {
return response
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,15 @@ class SettingsRepository : ISettingsRepository, KoinComponent {
enum class Settings(val dataStoreKey: Preferences.Key<Boolean>) {
Map(booleanPreferencesKey("map_debug")),
Search(booleanPreferencesKey("search_featureFlag")),
DynamicMapKey(booleanPreferencesKey("map_dynamicKey_featureFlag")),
}

data class Setting(val key: Settings, var isOn: Boolean)

class MockSettingsRepository(private var settings: Set<Setting> = setOf()) : ISettingsRepository {
override suspend fun getSettings(): Set<Setting> {
return settings
}

override suspend fun setSettings(settings: Set<Setting>) {}
}
Loading

0 comments on commit 4ff11a2

Please sign in to comment.