Struggling with Platform-Specific Service Implementations
I'm still struggling a bit to find the cleanest pattern for dealing with services that need slightly different implementations when constructed on different platforms. Here's what I'm doing now:
The general pattern is to construct a shared implementation then use Layer.map to modify it with platform specific stuff. Does that seem like a good approach or is there a cleaner way to do "platform specific implementation details" with Layers?
//DeviceStateService.ts -- shared implementation
export interface DeviceStateService {
_tag: "DeviceStateService"
isOnline: ObservableSubscriptionRef<boolean>
isUpdateAvailable: ObservableSubscriptionRef<boolean>
platform: PlatformType
isOnReactNative(): boolean
appStateRef: ObservableSubscriptionRef<AppStateStatus>
}
export const DeviceStateService = Context.Tag<DeviceStateService>()
export interface DeviceStateConfig {
platform: PlatformType
}
export const make = ({ platform }: DeviceStateConfig) =>
Effect.gen(function* (_) {
const isOnline = yield* _(observableSubscriptionRef(`isOnline`, () => true))
const isUpdateAvailable = yield* _(observableSubscriptionRef(`isUpdateAvailable`, () => false))
const appStateRef = yield* _(observableSubscriptionRef<AppStateStatus>(`appStateRef`, () => "active"))
const isOnReactNative = () => platform !== "web" && platform !== "server"
return {
_tag: "DeviceStateService" as const,
isOnline,
isUpdateAvailable,
platform,
isOnReactNative,
appStateRef,
}
})
export const makeLayer = (config: Config.Config.Wrap<DeviceStateConfig>) =>
Layer.effect(DeviceStateService, Effect.flatMap(Effect.config(Config.unwrap(config)), make))//DeviceStateService.ts -- shared implementation
export interface DeviceStateService {
_tag: "DeviceStateService"
isOnline: ObservableSubscriptionRef<boolean>
isUpdateAvailable: ObservableSubscriptionRef<boolean>
platform: PlatformType
isOnReactNative(): boolean
appStateRef: ObservableSubscriptionRef<AppStateStatus>
}
export const DeviceStateService = Context.Tag<DeviceStateService>()
export interface DeviceStateConfig {
platform: PlatformType
}
export const make = ({ platform }: DeviceStateConfig) =>
Effect.gen(function* (_) {
const isOnline = yield* _(observableSubscriptionRef(`isOnline`, () => true))
const isUpdateAvailable = yield* _(observableSubscriptionRef(`isUpdateAvailable`, () => false))
const appStateRef = yield* _(observableSubscriptionRef<AppStateStatus>(`appStateRef`, () => "active"))
const isOnReactNative = () => platform !== "web" && platform !== "server"
return {
_tag: "DeviceStateService" as const,
isOnline,
isUpdateAvailable,
platform,
isOnReactNative,
appStateRef,
}
})
export const makeLayer = (config: Config.Config.Wrap<DeviceStateConfig>) =>
Layer.effect(DeviceStateService, Effect.flatMap(Effect.config(Config.unwrap(config)), make))//AppDeviceStateService.ts -- the app specific version of DeviceStateService
export const makeLayer = () =>
pipe(
DeviceStateService.makeLayer(Config.succeed({ platform: getPlatformFromReactNative() })),
Layer.map(ds => pipe(
//app specific initialization goes here
//ex set up a stream from React Native's AppState.addEventListener
//to keep the appStateRef up to date
))
)//AppDeviceStateService.ts -- the app specific version of DeviceStateService
export const makeLayer = () =>
pipe(
DeviceStateService.makeLayer(Config.succeed({ platform: getPlatformFromReactNative() })),
Layer.map(ds => pipe(
//app specific initialization goes here
//ex set up a stream from React Native's AppState.addEventListener
//to keep the appStateRef up to date
))
)The general pattern is to construct a shared implementation then use Layer.map to modify it with platform specific stuff. Does that seem like a good approach or is there a cleaner way to do "platform specific implementation details" with Layers?
