New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
How to subscribe to multiple states separately in one class #318
Comments
I've run into this. I created "watcher" classes that focus on a particular part of the state tree. These classes can then be instantiated by the mother class, as you have listed here. When a state change fires, the "watcher" class calls into the mother class via a delegate method.
Mark
Mark S Broski
…________________________________
From: gkmrakesh <notifications@github.com>
Sent: Friday, January 12, 2018 11:14:11 AM
To: ReSwift/ReSwift
Cc: Subscribed
Subject: [ReSwift/ReSwift] How to subscribe to multiple states separately in one class (#318)
ReSwift Version: 4.0.0
Problem: I have to listen to multiple sub-states separately in one class
Currently, i am doing it like:
class Sample: StoreSubscriber {
init() {
// MARK: Subscriber of state 1, 2
appStore.subscribe(self) { state in
state.select { state in (state.state1, state.state2) }
}
}
// MARK: Listener for state 1, 2
func newState(state: (state1: State1, state2: State2) ) {
// process
}
}
How can i listen to each sub-state separately, so that i can act on only sub-state which get changed??
SomethingLike:
class Sample: StoreSubscriber {
init() {
// MARK: Subscriber
appStore.subscribe(self) { state in
state.select { state in state.state1 }
}
appStore.subscribe(self) { state in
state.select { state in state.state2 }
}
}
// MARK: Listener for state1
func newState(state: State1 ) {
// process
}
// MARK: Listener for state2
func newState(state: State2 ) {
// process
}
}
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub<#318>, or mute the thread<https://github.com/notifications/unsubscribe-auth/AB1EpXtVPtto8-M8idxqtflY2Gcpqamfks5tJ4TTgaJpZM4Rcgmx>.
|
You can't; you'd need to write 2 subscribers and 1 combined state change handler as separate objects. That being said, I wonder if your state is partitioned well if you run into this. Can you give a more concrete example? |
In order to subscribe to multiple states you'll need to use some sort of helper object. public class BlockSubscriber<S>: StoreSubscriber {
public typealias StoreSubscriberStateType = S
private let block: (S) -> Void
public init(block: @escaping (S) -> Void) {
self.block = block
}
public func newState(state: S) {
self.block(state)
}
} Then in your class, you can: class Sample {
private lazy var state1Subscriber: BlockSubscriber<State1> = BlockSubscriber(block: { [unowned self], state1 in
self.something = state1
})
private lazy var state2Subscriber: BlockSubscriber<State2> = BlockSubscriber(block: { [unowned self], state2 in
self.something2 = state2
})
init() {
// MARK: Subscriber
appStore.subscribe(self.state1Subscriber) { state in
state.select { state in state.state1 }
}
appStore.subscribe(self.state2Subscriber) { state in
state.select { state in state.state2 }
}
}
} |
Thanks for your quick reply. @mjarvis , Awesome, after months of struggle I can able to separate subscribers. But, issue remains same, When my sub-state changes, all subscribers (state1Subscriber, state2Subscriber in below code) getting notified. This brings me back to initial question. For example in below code, if "Counter" state changes then only state1Subscriber should get notified.
|
if mainStore.subscribe(self.state2Subscriber) { state in
state.select { state in state.counter2 }
}.skipRepeats(==) |
@mjarvis or someway to access oldState in newState would also work edit: maybe that's what newValues() is for. will try and let you guys know! (re:edit couldn't get this to work) |
That is not going to work. I think if you could explain a little more about your use case and also show us your state, we could help. One way, is to subscribe to just what you are interested in. Create smaller subscribers that listen to what's changing in the substate alone. But I would say you should design your state and actions such that this is not needed. For example, one way I handle this is, let's say on a screen you have a button which when clicked opens the camera. You dispatch an action "openCamera" when the button is touched. You have a flag in a substate "CameraState" that is "openCamera: Bool". This flag is set to true when the action openCamera is fired. Your view subscribes to changes in the substate "CameraState". If the flag openCamera is set to true you then work with the UIImagePickerController to show the camera. Once the camera is opened, you fire another action "cameraOpened" that sets "openCamera: Bool" flag in your state to false. This ensures that, if you receive any further updates to CameraState, you will not attempt to repeatedly show the camera. |
Sorry for delayed response, Thanks all for your valuable inputs and suggestions. I will check and get back on this. |
This solved part of my problem but there will still be a problem where I want to listen to state1 always(i.e only on changes to state1, even if same value get assigned again) and state2 only on new value. now only state2 values change still state1 get notified. is there any way to solve this problem? |
@mjarvis I think, for the sake of completeness, you should add the unsubscribe part of the code too. People may forget they have to call something other than |
@mohpor Subscriptions are |
This is a great use case candidate for the documentation. Is anybody willing to write 2 paragraphs about it and add it to the docs? :) |
I also was troubled to make subscriptions to multiple states. For more, I found a useful code snip. ReSwift+select |
@gkmrakesh @mjarvis @DivineDominion protocol HasAccountState {
var accountState: AccountState { get }
}
protocol HasHomeState {
var homeState: HomeState { get }
}
struct AppState: StateType, HasAccountState, HasHomeState {
let accountState: AccountState
let homeState: HomeState
} class ViewController: UIViewController, StoreSubscriber {
typealias StoreSubscriberStateType = HasAccountState & HasHomeState
override func viewDidLoad() {
super.viewDidLoad()
store.subscribe(self) { $0.select { $0 as StoreSubscriberStateType } }
}
func newState(state: StoreSubscriberStateType) {
let account = state.accountState
let home = state.homeState
}
} |
This is what I do as well, although I don't use protocols. I just create a struct representing the state I need in my view, with a constructor that takes the app state. Something like this:
in my view:
Consider the |
This is such a common pattern that we could provide a |
@danielmartinprieto I misunderstood how subscription works. Your solution is simple and the best. I think your must add this solution to the Readme. Thanks! |
@danielmartinprieto Would you? Otherwise, let's close this and leave it for later reference. I work with tuples, by the way, but in a similar fashion. I considered extracting the picking from the |
@DivineDominion You mean adding the subscribe method with the mapping fn built in or adding the example to the readme? |
Of I meant the README addition, if you think it's worth the effort. I am collecting how people use ReSwift to assemble a curated "cookbook", so if you rather leave this out of the README, I have a section about this in the guide anyway :) |
@DivineDominion done! |
currently using @mjarvis 's early solution and modifying it // MyClass.swift (init:)
self.unsubscribeLogin = mainPubSub.subscribe(topic: .loginState) {
[weak self] state in
guard let `self` = self else { return }
self.onStateChange(state)
}
self.unsubscribeRoute = mainPubSub.subscribe(topic: .routeState) {
[weak self] state in
guard let `self` = self else { return }
self.onStateChange(state)
}
// MyClass.swift (deinit:)
unsubscribeLogin()
unsubscribeRoute()
// Privates
private func onStateChange(state: LoginState) { state.isLoggedIn ? print("Y") : print("N") }
private func onStateChange(state: RouteState) { switch (state.currRoute) {...} } Anyone has suggestions on how to "map" an enumType (eg. TopicType like .loginState) to a generic type? I was thinking about creating a dict where key is enum and value is a |
ReSwift Version: 4.0.0
Problem: I have to listen to multiple sub-states separately in one class
Currently, i am doing it like:
How can i listen to each sub-state separately, so that i can act on only sub-state which get changed??
SomethingLike:
The text was updated successfully, but these errors were encountered: