Combine publisher to flow mapping

This commit is contained in:
John O'Reilly 2021-01-03 10:41:01 +00:00
parent b0693cf381
commit 6701e51b45
5 changed files with 89 additions and 32 deletions

View file

@ -16,6 +16,7 @@ import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.*
import org.koin.core.KoinComponent
import org.koin.core.inject
import kotlin.coroutines.CoroutineContext
class PeopleInSpaceRepository() : KoinComponent {
@ -82,21 +83,6 @@ class PeopleInSpaceRepository() : KoinComponent {
}
fun startObservingISSPosition(success: (IssPosition) -> Unit) {
logger.d { "startObservingISSPosition" }
issPositionJob = coroutineScope.launch {
pollISSPosition().collect {
success(it)
}
}
}
fun stopObservingISSPosition() {
logger.d { "stopObservingISSPosition, peopleJob = $issPositionJob" }
issPositionJob?.cancel()
}
fun pollISSPosition(): Flow<IssPosition> = flow {
while (true) {
val position = peopleInSpaceApi.fetchISSPosition().iss_position
@ -106,8 +92,32 @@ class PeopleInSpaceRepository() : KoinComponent {
}
}
val iosScope: CoroutineScope = object : CoroutineScope {
override val coroutineContext: CoroutineContext
get() = SupervisorJob() + Dispatchers.Main
}
fun iosPollISSPosition() = KotlinNativeFlowWrapper<IssPosition>(pollISSPosition())
companion object {
private const val POLL_INTERVAL = 10000L
}
}
class KotlinNativeFlowWrapper<T>(private val flow: Flow<T>) {
fun subscribe(
scope: CoroutineScope,
onEach: (item: T) -> Unit,
onComplete: () -> Unit,
onThrow: (error: Throwable) -> Unit
) = flow
.onEach { onEach(it) }
.catch { onThrow(it) }
.onCompletion { onComplete() }
.launchIn(scope)
}

View file

@ -17,6 +17,7 @@
1ABFB8C423AFF5CE003D807E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1ABFB8C323AFF5CE003D807E /* Assets.xcassets */; };
1ABFB8C723AFF5CE003D807E /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1ABFB8C623AFF5CE003D807E /* Preview Assets.xcassets */; };
1ABFB8CA23AFF5CE003D807E /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1ABFB8C823AFF5CE003D807E /* LaunchScreen.storyboard */; };
1AC2439025A1D57700F17D2F /* IssPositionPublisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AC2438F25A1D57700F17D2F /* IssPositionPublisher.swift */; };
1E1255057DE614781855FC02 /* libPods-PeopleInSpaceSwiftUI.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 28681F6577A67864E2C2D9B4 /* libPods-PeopleInSpaceSwiftUI.a */; };
/* End PBXBuildFile section */
@ -33,6 +34,7 @@
1ABFB8C623AFF5CE003D807E /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
1ABFB8C923AFF5CE003D807E /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
1ABFB8CB23AFF5CE003D807E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
1AC2438F25A1D57700F17D2F /* IssPositionPublisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IssPositionPublisher.swift; sourceTree = "<group>"; };
28681F6577A67864E2C2D9B4 /* libPods-PeopleInSpaceSwiftUI.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-PeopleInSpaceSwiftUI.a"; sourceTree = BUILT_PRODUCTS_DIR; };
56CD7101BBBC60F561BFB049 /* Pods-PeopleInSpaceSwiftUI.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PeopleInSpaceSwiftUI.release.xcconfig"; path = "Target Support Files/Pods-PeopleInSpaceSwiftUI/Pods-PeopleInSpaceSwiftUI.release.xcconfig"; sourceTree = "<group>"; };
E9A29AFE5FFFB564C509840A /* Pods-PeopleInSpaceSwiftUI.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PeopleInSpaceSwiftUI.debug.xcconfig"; path = "Target Support Files/Pods-PeopleInSpaceSwiftUI/Pods-PeopleInSpaceSwiftUI.debug.xcconfig"; sourceTree = "<group>"; };
@ -99,6 +101,7 @@
1ABFB8C523AFF5CE003D807E /* Preview Content */,
1ABD44F923B00008008387E3 /* ViewModel.swift */,
1AA82EEC25052E1E00193051 /* ImageView.swift */,
1AC2438F25A1D57700F17D2F /* IssPositionPublisher.swift */,
);
path = PeopleInSpaceSwiftUI;
sourceTree = "<group>";
@ -219,6 +222,7 @@
buildActionMask = 2147483647;
files = (
1AA82EED25052E1E00193051 /* ImageView.swift in Sources */,
1AC2439025A1D57700F17D2F /* IssPositionPublisher.swift in Sources */,
1ABFB8BE23AFF5CC003D807E /* AppDelegate.swift in Sources */,
1ABFB8C023AFF5CC003D807E /* SceneDelegate.swift in Sources */,
1ABD44FA23B00008008387E3 /* ViewModel.swift in Sources */,

View file

@ -8,25 +8,24 @@ struct ContentView: View {
var body: some View {
NavigationView {
VStack {
let issPosition = String(format: "ISS Position = (%f, %f)", peopleInSpaceViewModel.issPosition.latitude, peopleInSpaceViewModel.issPosition.longitude )
let issPosition = peopleInSpaceViewModel.issPosition
let issPositionString = String(format: "ISS Position = (%f, %f)", issPosition.latitude, issPosition.longitude )
HStack {
Text(issPosition)
Text(issPositionString)
}
.padding(EdgeInsets(top: 18, leading: 16, bottom: 0, trailing: 16))
List(peopleInSpaceViewModel.people, id: \.name) { person in
NavigationLink(destination: PersonDetailsView(peopleInSpaceViewModel: self.peopleInSpaceViewModel, person: person)) {
NavigationLink(destination: PersonDetailsView(peopleInSpaceViewModel: peopleInSpaceViewModel, person: person)) {
PersonView(peopleInSpaceViewModel: self.peopleInSpaceViewModel, person: person)
}
}
.navigationBarTitle(Text("People In Space"))
.onAppear {
self.peopleInSpaceViewModel.startObservingPeopleUpdates()
self.peopleInSpaceViewModel.startObservingISSPosition()
}.onDisappear {
self.peopleInSpaceViewModel.stopObservingPeopleUpdates()
self.peopleInSpaceViewModel.stopObservingISSPosition()
}
}
}
@ -59,7 +58,7 @@ struct PersonDetailsView: View {
Text(person.name).font(.title)
ImageView(withURL: peopleInSpaceViewModel.getPersonImage(personName: person.name), width: 240, height: 240)
Text(peopleInSpaceViewModel.getPersonBio(personName: person.name)).font(.body)
Spacer()
}

View file

@ -0,0 +1,46 @@
import Combine
import common
public struct IssPositionPublisher: Publisher {
public typealias Output = IssPosition
public typealias Failure = Never
private let repository: PeopleInSpaceRepository
public init(repository: PeopleInSpaceRepository) {
self.repository = repository
}
public func receive<S: Subscriber>(subscriber: S) where S.Input == IssPosition, S.Failure == Failure {
let subscription = IssPositionSubscription(repository: repository, subscriber: subscriber)
subscriber.receive(subscription: subscription)
}
final class IssPositionSubscription<S: Subscriber>: Subscription where S.Input == IssPosition, S.Failure == Failure {
private var subscriber: S?
private var job: Kotlinx_coroutines_coreJob? = nil
private let repository: PeopleInSpaceRepository
init(repository: PeopleInSpaceRepository, subscriber: S) {
self.repository = repository
self.subscriber = subscriber
job = repository.iosPollISSPosition().subscribe(
scope: repository.iosScope,
onEach: { position in
subscriber.receive(position!)
},
onComplete: { debugPrint("onComplete") },
onThrow: { error in debugPrint(error) }
)
}
func cancel() {
subscriber = nil
job?.cancel(cause: nil)
}
func request(_ demand: Subscribers.Demand) {}
}
}

View file

@ -1,4 +1,5 @@
import Foundation
import Combine
import common
@ -6,9 +7,14 @@ class PeopleInSpaceViewModel: ObservableObject {
@Published var people = [Assignment]()
@Published var issPosition = IssPosition(latitude: 0.0, longitude: 0.0)
private var subscription: AnyCancellable?
private let repository: PeopleInSpaceRepository
init(repository: PeopleInSpaceRepository) {
self.repository = repository
subscription = IssPositionPublisher(repository: repository)
.assign(to: \.issPosition, on: self)
}
func startObservingPeopleUpdates() {
@ -21,17 +27,6 @@ class PeopleInSpaceViewModel: ObservableObject {
repository.stopObservingPeopleUpdates()
}
func startObservingISSPosition() {
repository.startObservingISSPosition(success: { data in
self.issPosition = data
})
}
func stopObservingISSPosition() {
repository.stopObservingISSPosition()
}
func getPersonBio(personName: String) -> String {
return repository.getPersonBio(personName: personName)
}
@ -40,5 +35,8 @@ class PeopleInSpaceViewModel: ObservableObject {
return repository.getPersonImage(personName: personName)
}
}