rxswiftcommunity / rxflow Goto Github PK
View Code? Open in Web Editor NEWRxFlow is a navigation framework for iOS applications based on a Reactive Flow Coordinator pattern
License: MIT License
RxFlow is a navigation framework for iOS applications based on a Reactive Flow Coordinator pattern
License: MIT License
Why does not support iOS9.0?
I reviewed the code from RxFlowDemo, but there's no example of how to handle a custom action from a UITabBarController.
To handle a custom action we commonly implement the following function from UITabBarControllerDelegate
:
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
if viewControllers?.index(of: viewController) == 2 {
// TODO: Open camera from TabCoordinator
return false
}
return true
}
How would this be done in RxFlow?
Hello!
I am trying to get Deep Linking to work with RxFlow. I have an AppStepper
that has a method attemptNavigation(to:DeepLinkable) -> Bool
which gets called for example when a link with the apps URL scheme is clicked:
extension AppDelegate {
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
guard let appStepper = appStepper else { return false }
return appStepper.attemptNavigation(to: url)
}
}
My AppStepper
implementation looks as follows where DeepLinkService
is responsible for converting DeepLinkable
items to Step
:
final class AppStepper: Stepper {
private let deepLinkService: DeepLinkServiceProtocol
private let container: Container
init(container: Container, deepLinkService: DeepLinkServiceProtocol) {
self.deepLinkService = deepLinkService
self.container = container
step(to: initialStep(container: container))
}
}
//MARK: - Deep Link Handling
extension AppStepper {
@discardableResult func attemptNavigation(to maybeDeepLink: DeepLinkable) -> Bool {
guard let deepLinkType = deepLinkService.parse(maybeDeepLink) else { return false }
let _nextStep = nextStep(for: deepLinkType, in: container)
step(to: _nextStep)
return true
}
func nextStep(for deepLinkType: DeepLinkType, in container: Container) -> AppStep {
let isLoggedIn = (container ~> KeyValueStore<KeyChainKey>.self).hasString(for: .token(.authentication))
let nextStep = isLoggedIn ? deepLinkType.step : .authStart
return nextStep
}
}
Then I have the tabBar solution that @Sajjon implemented in this PR: #41 to show .multiple
NextFlowItems
for each tab after the user is logged in.
The problem I'm facing is that step(to: _nextStep)
in attemptNavigation(to:DeepLinkable)
never triggers the navigate(to:Step)
method of my AppFlow
and therefore I can't do any navigation from Deep Links.
Do you have any suggestions on how I can implement this kind of Deep Link handling?
Hi there,
I was wondering if it is possible to navigate to the next flow item with a simple call like step.next. As far as I know and tried out several things, I always have to know what the next step should be. We have a big onboarding navigation where we just want to set the order of the steps within the step enum and then just want to call step.next or something similar.
Any ideas?
Many thanks,
FinDev
I'm starting a new project and I try to use RxFlow 😃
I cant find a simple solution how to best handle UIAlertControllers, without a taste of over engineering my flows. Can you give me some advise how to best handle them?
Thanks in advance!
In this flow, in particular the func navigate(to:)
I'm passing data like, 3 times...
Is there anyway to avoid passing data so many times? like here:
func navigate(to step:Step) -> NextFlowItems {
guard let step = step as? AppStep else { return NextFlowItems.stepNotHandled }
switch step {
case .destinationsNearMe(let lat, let long):
return navigateToDestinationsNearMeList(with: lat, with: long)
default:
return NextFlowItems.stepNotHandled
}
}
private func navigationToDashboardScreen() -> NextFlowItems {
}
private func navigateToDestinationsNearMeList(with lat: Double, with long: Double) -> NextFlowItems {
This same data is being passed three times,
case .destinationsNearMe(let lat, let long):
return navigateToDestinationsNearMeList(with: lat, with: long)
func navigateToDestinationsNearMeList(with lat: Double, with long: Double)
can you recommend or show an example of an RX way or a more efficient way to pass the data,
from the VC to the VM then to the Services/Data Providers/Networking, that need some
current user/device state info, to kick off processing.
The navigate(to) fired multiple times if the nextStepper is set by a shared insance.
please check below code. The screen1 presented as expected, but when the viewModel set the state to the screen2, the navigate(to:) fired twice. It means the screen2 was pushed twice into the navigation stack.
final class RegistrationFlow: Flow {
var root: Presentable {
return self.rootViewController
}
let viewModel = SharedViewModel()
private let rootViewController = UINavigationController()
init() { }
func navigate(to step: Step) -> NextFlowItems {
guard let step = step as? DLStep else { return .none }
switch step {
case .screen1:
return navigateToScreen1()
case .screen2:
return navigateToScreen2()
case .screen3:
return navigateToScreen3()
default:
return .none
}
}
private func navigateToScreen1() -> NextFlowItems {
let viewController = Screen1ViewController()!
viewController.viewModel = viewModel
rootViewController.setViewControllers([viewController], animated: false)
return .one(flowItem: NextFlowItem(nextPresentable: viewController, nextStepper: viewModel))
}
private func navigateToScreen2() -> NextFlowItems {
let viewController = Screen2ViewController()!
viewController.viewModel = viewModel
rootViewController.show(viewController, sender: self)
return .one(flowItem: NextFlowItem(nextPresentable: viewController, nextStepper: viewModel))
}
private func navigateToScreen3() -> NextFlowItems {
let viewController = Screen3ViewController()!
viewController.viewModel = viewModel
rootViewController.show(viewController, sender: self)
return .one(flowItem: NextFlowItem(nextPresentable: viewController, nextStepper: viewModel))
}
}
Shouldn't it be integrated without depending on another library?
My app has tab bar navigation with two tabs (Profile and Feed).
AppFlow class implement code for tabBar:
class AppFlow: Flow {
var root: Presentable {
return self.rootWindow
}
private let rootWindow: UIWindow!
private let tabBarController: UITabBarController!
init(with window: UIWindow) {
self.rootWindow = window
self.tabBarController = UITabBarController()
}
func navigate(to step: Step) -> NextFlowItems {
guard let step = step as? MyStep else {
return NextFlowItems.none
}
switch step {
case .introComplete:
return navigationToMain()
default:
return NextFlowItems.none
}
}
private func navigationToMain() -> NextFlowItems {
let feedFlow = FeedFlow()
let profileFlow = ProfileFlow()
Flows.whenReady(flow1: feedFlow, flow2: profileFlow, block: { [unowned self] (root1: UINavigationController, root2: UINavigationController) in
let tabBarItem1 = UITabBarItem(title: "Profile", image: nil, selectedImage: nil)
let tabBarItem2 = UITabBarItem(title: "Feed", image: nil, selectedImage: nil)
root1.tabBarItem = tabBarItem1
root2.tabBarItem = tabBarItem2
self.tabBarController.setViewControllers([root1, root2], animated: false)
self.rootWindow.rootViewController = self.tabBarController
})
return NextFlowItems.multiple(flowItems: [
NextFlowItem(nextPresentable: profileFlow, nextStepper: OneStepper(withSingleStep: MyStep.profileShow)),
NextFlowItem(nextPresentable: feedFlow, nextStepper: OneStepper(withSingleStep: MyStep.feedShowScreen))
])
}
}
FeedFlow class implement navigation for feed:
class FeedFlow: Flow {
var root: Presentable {
return self.rootViewController
}
private let rootViewController = UINavigationViewController()
func navigate(to step: Step) -> NextFlowItems {
guard let step = step as? MyStep else {
return NextFlowItems.none
}
switch step {
case .feedPickNews(let news):
return navigationToOpenNews(with: news)
case .feedShowScreen:
return navigationToShow()
case .feedSaveNews:
return NextFlowItems.end(withStepForParentFlow: MyStep.openProfileTab)
default:
return NextFlowItems.none
}
}
private func navigationToShow() -> NextFlowItems {
let feedViewController = StoryboardScene.Feed.feedViewController.instantiate()
let feedViewModel = FeedViewModel()
feedViewController.viewModel = feedViewModel
self.rootViewController.pushViewController(feedViewController, animated: true)
return NextFlowItems.one(flowItem: NextFlowItem(nextPresentable: feedViewController, nextStepper: feedViewModel))
}
private func navigationToOpenNews(with news: News) -> NextFlowItems {
let viewController = StoryboardScene.Feed.feedInfoViewController.instantiate()
let viewModel = FeedInfoViewModel(news: news)
viewController.viewModel = viewModel
self.rootViewController.pushViewController(viewController, animated: true)
return NextFlowItems.one(flowItem: NextFlowItem(nextPresentable: viewController, nextStepper: viewModel))
}
}
And I want to realize this flow:
Open app (Profile
tab) –> Choose Feed
tab –> Pick any news cell –> On News
screen click button Save news
–> ... and I want to dismiss navigation from Feed Info
screen (.feedSaveNews
) to root and locate Profile
screen back.
First, I tried to finish (.end(...)
) Feed flow, it works but further, I can't open any news in Feed
. Then I tried to create one more flow for FeedInfoViewController, that I could be finished FeedInfoFlow, but I stuck with navigations problems.
Can you advise something for my case?
Is there a way to combine RxReduce + RxFlow to get something like ReSwift ?
Having both well integrated would really enhance Rx project in my opinion.
Hi. I just recently started looking at RxFlow and have a question. I see that in RxFlow/Stepper.swift that there is a class named NoneStepper
, but that class is not public. I had a case tonight where I wanted to pass an empty Stepper
object. I don't see that class being used anywhere in the framework. It seems weird to be private to the framework and not exposed. Is it meant to be public?
Great framework. Thanks for contributing it to the community.
pod update
gives me 1.0.4
As RxSwift tips, nesting subscription should be avoided. While implementing CompositeStepper
PR #5, I see you are triple subscribed in Coordinator.swift
and it leads to a crash. I'm refactoring this function, so I open an issue to open discuss if you have any ideas?
https://github.com/ReactiveX/RxSwift/blob/master/Documentation/Tips.md
Hi in my current project I need to swap between 2 Flows attached to the root Window, in the main flow I keep checking the user signing status to decide if in need show one flow or other , but when I try to swap between flows the steps emited from the main flow are ignored becouse the window.rxVisible
emit completed
just after the window become keyAndVisible so the cordinator ignores that steps
Hi,
I am trying to use RxFlow (I am new to RxSwift too) for a new project and I would like to hide a tab bar. The only solution I found so far is to have a global variable like
let DashboardRootViewController = UITabBarController()
then DashboardRootViewController.tabBar.isHidden = true
Is there a nicer way to do it?
Hi,
Currently the coordinator object has only 2 rx streams (willNavigate, didNavigate) that are specialized with 2 Strings that describes the Flow and the Step.
var willNavigate: Observable<(String, String)>
var didNavigate: Observable<(String, String)>
These are very useful for "Analytics purposes".
My idea is to add 2 more streams, that are basically the same but instead to have 2 Strings as specialized Types, they will have a Flow and a Step
var willNavigate: Observable<(Flow, Step)>
var didNavigate: Observable<(Flow, Step)>
These streams are very powerful because allow the coordinator to act on the Flow or on the Step during the Navigation. For example the coordinator can then set a specific property by checking Protocol Conformance acting like as a "Dependency Resolver".
Moreover this extend the analytics capabilities of the current solution. So it's possible to have a custom logging convention per Flow or per Step.
If you need more detailed explanation don't hesitate to contact me 😃
With github "RxSwiftCommunity/RxFlow"
in my path/to/myproject/Cartfile, carthage update
[1] fails without any useful information in logs (both stdout and xcodebuild log file).
The solution was to cd /path/to/myproject/Carthage/Checkouts/RxFlow/RxFlow && carthage update
[1]. After this, running carthage update
[1] again in /path/to/myproject succeeded as expected.
It should not be ideally necessary for the developer to do a nested installation.
[1] carthage update --platform iOS --configuration Release --no-use-binaries --cache-builds
We have a view in our app, which is visible on each screen. This view can present some views modally or push them onto the navigation stack.
Any advice on how to create Flows in this scenario? It would be nice if all steps from this floating view would be handled by some RootFlow from where all the other views are presented, so the RootFlow would dismiss everything and present new view if needed.
When this is returned, what does this mean?
return Flowable.noFlow
In Flow.swift
there is a Flows
class:
/// Utility functions to synchronize Flows readyness
public class Flows {
// swiftlint:disable line_length
/// Allow to be triggered only when Flows given as parameters are ready to be displayed.
/// Once it is the case, the block is executed
///
/// - Parameters:
/// - flow1: first Flow to be observed
/// - flow2: second Flow to be observed
/// - flow3: third Flow to be observed
/// - block: block to execute whenever the Flows are ready to use
public static func whenReady<RootType1: UIViewController, RootType2: UIViewController, RootType3: UIViewController> (flow1: Flow,
flow2: Flow,
flow3: Flow,
block: @escaping (_ warp1Root: RootType1, _ warp2Root: RootType2, _ warp3Root: RootType3) -> Void) {
_ = Observable<Void>.zip(flow1.rxFlowReady.asObservable(), flow2.rxFlowReady.asObservable(), flow3.rxFlowReady.asObservable()) { (_, _, _) in
return Void()
}.take(1).subscribe(onNext: { (_) in
guard let root1 = flow1.root as? RootType1,
let root2 = flow2.root as? RootType2,
let root3 = flow3.root as? RootType3 else {
fatalError ("Type mismatch, Flows roots types do not match the types awaited in the block")
}
block(root1, root2, root3)
})
}
......
}
is there no problem without caring the returned Disposable
value?
How to coordinate more than 3 Flows
when using a UITabBarController
with 4 or 5 tabBars
?
I tried to overwrite the Flows.whenReady
using extension
, but it thrown error 'rxFlowReady' is inaccessible due to 'internal' protection level
.
There is a run script that expect the user to have a carthage (/usr/local/bin/carthage
) folder. The projects won't build because of this
I'm using RxFlow in my app, and I'm admittedly behind on my unit test plans. I'm trying to catch up and create unit tests for my flows. When creating a unit test for the Flow.navigate(to:)
method though, I'm seeing that there's no good way to really test the result. I can test whether the NextFlowItems
result is one
, none
, or many
, but there's no way to test to validate that the right flow or view controller is being returned because the members of NextFlowItem
are not public.
Is there a recommended way for building unit tests involving NextFlowItem
and NextFlowItems
?
Hi, first off—thanks creating RxFlow! I'm using it for the first time and it looks pretty good so far. I'm running into a weird issue that seems to be linked to returning a view controller as a NextFlowItem
in a flow. If I try to load a nib programmatically at any initial stage of the view controller lifecycle (viewDidLoad
, viewWillAppear
, and viewDidAppear
) and attempt to get a reference to our module's bundle by using Bundle(for: type(of: self))
(self
being the view controller) a reference to our app's target is returned instead of our framework module.
Example:
From my flow function that returns NextFlowItems
from a Step
enum case:
private func navigateToMyNextScreen() -> NextFlowItems {
// Create view controller, add view model, and push it with the nav controller
let nextFlowItem = NextFlowItem(nextPresentable: myViewController, nextStepper: myViewController)
return NextFlowItems.one(flowItem: nextFlowItem)
}
Output of at viewDidLoad
, viewWillAppear
, and viewDidAppear
in myViewController
from Bundle(for: type(of: self)).resourcePath
:
"/Users/[...]/Library/Developer/CoreSimulator/Devices/01802E25-FD20-43A8-9729-831B620F0281/data/Containers/Bundle/Application/310DB524-2ACA-4FD6-9168-9A0102D43239/[...].app"
However, if I just return NextFlowItems.none
from navigateToMyNextScreen()
(i.e., the view controller isn't registered):
private func navigateToMyNextScreen() -> NextFlowItems {
// Create view controller, add view model, and push it with the nav controller
return NextFlowItems.none
}
And the correct bundle resourcePath
is given:
Output at viewDidLoad
, viewWillAppear
, and viewDidAppear
in myViewController
from Bundle(for: type(of: self)).resourcePath
:
"/Users/[...]/Library/Developer/Xcode/DerivedData/[...]-flzbuuwuhjeoxpeasujpzylznujt/Build/Products/Debug-iphonesimulator/[...].framework"
This issue appears similar to jverkoey/iOS-Framework#107, although from my understanding in Swift 4 type(of: self)
is supposed to be equivalent to Obj-C [self class]
.
We're using RxFlow a little differently from the sample projects, so not sure if that could be causing it. As we're testing it out on a greenfield feature inside a mature app, we're not initializing it with the app's UIWindow
; instead, we're using it in one tab of a UITabViewController
and it's only one flow. The flow returns a UINavigationController
as its root
property which is then added to the tab that's displayed. We also have multiple frameworks that we use in the same project, but RxFlow is only being used inside one framework.
Is there anything you can think of in either RxFlow or our use case that could cause this to occur? It's easy enough to work around but was wondering what was causing it, as it's a rather weird bug. Thanks!
Hi Guys,
Thanks for your framework!
And I am a little confused how to solve a problem:
I have a MainViewController with child view controller inside.
Also the main ViewController have a top left button clicking on showed LeftMenu.
the problem is next: user can click on buttons inside child view controller that should set next step,
and user can click on buttona inside left menu, that should set the same next step as inside child view controller.
On clicking button the information should changed in childviewcontroller. No metter where button was clicked.
I set in code next string:
return NextFlowItems.multiple(flowItems: [
NextFlowItem(nextPresentable: modulesFlow, nextStepper: leftMenuViewController),
NextFlowItem(nextPresentable: modulesFlow, nextStepper: OneStepper(withSingleStep: QRStep.module(0))),
]
)
But with such code clicking inside children view controller works good, but clicking on buttons in Left menu rxflow did not see. How can I achieve needed result?
Thanks.
I was trying this library and was thinking about app flows and DI, so I have a question: is it bad practice to declare enum cases with associated values (view models) to pass them from Steppers? It will retain the most recent step's value because BehaviorRelay will hold it, but maybe it can be changed somehow, if the idea isn't bad, if it is please explain why.
Hey, in this Example by @sajjon
I like the way he spices up the Steps enum.
enum AppStep: Step {
case start // Never Ending Story
indirect case first(First)
static var firstStart: AppStep { return .first(.start) }
enum First: Step {
case start, done
case applePay, applePayDone
case permissions, permissionsDone
}
indirect case version(Version)
static var versionStart: AppStep { return .version(.start) }
enum Version: Step {
case start, done
case forceUpdate, forceUpdateBlock
case onboarding, onboardingDone
}
.....
case .version(.start): return navigateToVersionStartScreen()
when you're working with that enum
you type,
Flow(.step)
super clear!
Vs. the way original Steps
enum DemoStep: Step {
case onboarding
case apiKey
case apiKeyIsComplete
case login
case loginIsComplete
case dashboard
case movieList
case moviePicked (withMovieId: Int)
case castPicked (withCastId: Int)
case settings
case settingsDone
case about
}
Is there anyway to introduce this into the frameworks,
theres some parts in this branch, feature/onboardingFlow
I don't get enough to incorporate the two well, just yet...
Everytime, i try to build a Demo Project or a Project I create with RxFlow, I get
No module 'RxFlow'
I'm using cocoapods
pod 'RxFlow'
It took me a little bit to understand the concepts behind this library and integrate them into my
projects, the time has come to build. But, I am now facing issues getting RxFlow to import.
I have tried using cocoapods and Carthage, on fresh projects, existing projects and the demo project, to no avail.
I cloned your example project, deleted Carthage support and created my own Podfile and tried to compile, but I occured an errors like this:
Type 'WatchedFlow' does not conform to protocol Flow
and etc Flow
's
When I created a new empty project, linked pods, created my own Flow class like AppFlow, I also occured this error.
My environment:
My Podfile:
use_frameworks!
target 'RxFlowDemo' do
pod 'RxSwift'
pod 'RxCocoa'
pod 'RxFlow'
pod 'Reusable'
end
What I'm doing wrong?
I want to show several temporary flows before I will show the MainFlow
(user signed in and show the main screen in the app).
The AppDelegate
's Coordinator
coordinates to the AppFlow
in application:didFinishLaunchingWithOptions
.
The AppFlow
will then navigate to several temporary flows before MainFlow
, where each Flow has several screens (steps)
Lets say that we have these flow chart:
AppStart> AppFlow --set-root--> FirstRunFlow --set-root--> SignInFlow --set-root--> MainFlow
I do not want the user to be able to navigate back from SignInFlow
to FirstRunFlow
. What makes sense is to completely replace the NavigationStack (the UINavigationController
and its UIViewController
s) of SignInFlow
with a new UINavigationController
when SignInFlow
is done (last step reached) .
So for the temporary flows I guess I want to replace the window
in AppDelegate
with each temporary flows' UINavigationController
.
I have created an example app but it is not working fully because I have not really been able to dismiss these temporary flows. Their deinit
does not get called.
In the README it says
In order to improve RxFlow consistency, it seemed obvious that a Flow that presents a UIViewController also HAS to dismiss it. In previous versions, a child Flow was dismissed by itself and not by the Flow that presented it. In the Demo Application you will find an example with the WishlistFlow dismissing the SettingsFlow for the settingsDone Step.
This sounds very relevant to what I want to do. I WANT to dismiss a Flow manually I guess? Because right now the framework seems to be working fine when Presentable
s are presented or pushed.
How can I handle the scenario where I have several temporary Flow
s I want to go through but disabling the possibility of navigating back and ultimately land on a MainFlow
where the user spends most of her time (logged in and consuming content).
My example app is rather ambitious, please have a look: https://github.com/Sajjon/RxFlowExample
What to do? :)
Hello, first of all congratulate you for your excellent work with RxFlow, I am integrating it in my project and the problem is given when the UIViewController root of a flow presents a UIAlertController, after the UIAlertController is dismissed these lines are executed in Coordinador.swift
It is called and therefore the flow ends, this issues no happen if the UIAlertController is presented by other UIViewController
excuse my English
Sorry this issue was solved with this pull request #27
I just wondered if there exists a specific reason for naming the variable in the function Flows.whenReady
flow1
and not just flow
for convenience? :)
I fully understand the notion when it comes to more than one flow.
We wrote a very little extension for that, cause we found it fresher to read:
extension Flows {
public static func whenReady<RootType: UIViewController> (flow: Flow, block: @escaping (_ flowRoot: RootType) -> Void) {
whenReady(flow1: flow, block: block)
}
}
Hey,
I really like your framework and we tried using it in one of our apps. Right now we are facing a problem when showing a simple alert view and because of that I started investigating what was going on in the framework and I think I found a bug in the dismissed
observable causing multiple other problems.
How to reproduce:
Show an alert in an UINavigationController
Dismissing the alert leads to an event being fired for the dismissed
observable of the UINavigationController.
This leads to multiple other issues, because the 'rxDismissed' observable will be fired therefore as well for the flow and our flow doesn't react to any event anymore even tho this view is still there.
Are the docs hosted anywhere? Haven't found the place yet :)
Btw really nice library, love how it helps to reduce the boilerplate in our platform.
Here's my MainFlow.swift
:
final class MainFlow: Flow {
var root: UIViewController {
return self.rootViewController
}
private let rootViewController: UITabBarController
init() {
self.rootViewController = UITabBarController().then {
$0.tabBar.isTranslucent = false
$0.tabBar.barTintColor = .rh_primary
}
}
func navigate(to step: Step) -> [NextFlowItem] {
guard let step = step as? AppStep else { return NextFlowItem.noNavigation }
switch step {
case .main:
return navigationToMainScreen()
default:
return NextFlowItem.noNavigation
}
}
private func navigationToMainScreen() -> [NextFlowItem] {
let firstFlow = FirstFlow()
let secondFlow = SecondFlow()
let thirdFlow = ThirdFlow()
let fourthFlow = FourthFlow()
Flows.whenReady(flows: [firstFlow, secondFlow, thirdFlow, fourthFlow]) { roots in
self.rootViewController.setViewControllers(
roots.map { root in
return (root as! UINavigationController).then {
$0.navigationBar.isTranslucent = false
$0.navigationBar.barTintColor = .rh_primary
}
},
animated: false)
// test the function works or not!
print("whenReady")
}
return [
NextFlowItem(nextPresentable: firstFlow, nextStepper: OneStepper(withSingleStep: AppStep.first)),
NextFlowItem(nextPresentable: secondFlow, nextStepper: OneStepper(withSingleStep: AppStep.second)),
NextFlowItem(nextPresentable: thirdFlow, nextStepper: OneStepper(withSingleStep: AppStep.third)),
NextFlowItem(nextPresentable: fourthFlow, nextStepper: OneStepper(withSingleStep: AppStep.fourth))]
}
}
And my Podfile
:
pod 'RxFlow', :git => 'https://github.com/RxSwiftCommunity/RxFlow.git', :branch => 'develop'
I found that the function whenReady
is not work, and did not throw any errors. But when I change pods to commit: c488cdd
like this:
pod 'RxFlow', :git => 'https://github.com/RxSwiftCommunity/RxFlow.git', :commit => 'c488cdd'
While changing NextFlowItem
to Flowable
, noNavigation
to noFlow
, it just works fine, and the console print whenReady
.
I have checked the source code, but not find the reason.
Help me, please.
Hi guys,
From what I understand looking at code of RxFlow, step will be subscribed when viewController didAppear.
Why rxflow dont do it earlier, for example on viewWillAppear or more earlier?
Thanks.
I received this warning whenever I nav toNextFlowItems.end(withStepForParentFlow: MZStep.XXX)
⚠️ Reentrancy anomaly was detected.
> Debugging: To debug this issue you can set a breakpoint in /Users/derekxinzhewang/Companies/MobWorx/demo/MobCast/Pods/RxSwift/RxSwift/Rx.swift:97 and observe the call stack.
> Problem: This behavior is breaking the observable sequence grammar. `next (error | completed)?`
This behavior breaks the grammar because there is overlapping between sequence events.
Observable sequence is trying to send an event before sending of previous event has finished.
> Interpretation: This could mean that there is some kind of unexpected cyclic dependency in your code,
or that the system is not behaving in the expected way.
> Remedy: If this is the expected behavior this message can be suppressed by adding `.observeOn(MainScheduler.asyncInstance)`
or by enqueing sequence events in some other way.
I have looked #24. But I didn't trigger step inside flow.
My Flow is simply modified version of RxFlowDemo
AppFlow.swift
class AppFlow: Flow {
var root: Presentable {
return self.rootWindow
}
private let rootWindow: UIWindow
private let services: AppServices
init(withWindow window: UIWindow, andServices services: AppServices) {
self.rootWindow = window
self.services = services
}
func navigate(to step: Step) -> NextFlowItems {
guard let step = step as? MZStep else { return NextFlowItems.none }
switch step {
case .onboarding, .mainCompleted:
print(".onboarding, .mainCompleted")
return navigationToOnboardingFlow()
case .onboardingCompleted, .main:
print(".onboardingCompleted, .main")
return navigationToMainFlow()
default:
return NextFlowItems.none
}
}
private func navigationToOnboardingFlow() -> NextFlowItems {
let onboardingFlow = OnboardingFlow(withServices: self.services)
Flows.whenReady(flow1: onboardingFlow) { [unowned self] (root) in
self.rootWindow.rootViewController = root
}
return NextFlowItems.one(flowItem: NextFlowItem(nextPresentable: onboardingFlow, nextStepper: OneStepper(withSingleStep: MZStep.signin)))
}
private func navigationToMainFlow() -> NextFlowItems {
let mainFlow = MainFlow(withServices: self.services)
Flows.whenReady(flow1: mainFlow) { [unowned self] (root) in
self.rootWindow.rootViewController = root
}
return NextFlowItems.one(flowItem: NextFlowItem(nextPresentable: mainFlow, nextStepper: OneStepper(withSingleStep: MZStep.main)))
}
}
struct AppServices {
}
class AppStepper: Stepper {
init(withServices services: AppServices) {
if !AWSSignInManager.sharedInstance().isLoggedIn {
self.step.accept(MZStep.onboarding)
} else {
self.step.accept(MZStep.main)
}
}
}
OnboardingFlow.swift
class OnboardingFlow: Flow {
var root: Presentable {
return self.rootViewController
}
private lazy var rootViewController: UINavigationController = {
let viewController = UINavigationController()
return viewController
}()
private let services: OnboardingServices
init(withServices services: AppServices) {
self.services = OnboardingServices(services)
}
func navigate(to step: Step) -> NextFlowItems {
guard let step = step as? MZStep else { return NextFlowItems.none }
switch step {
case .signin:
return navigationToSigninScreen()
case .validatePasscode(let user):
return navigationToSigninPasscodeScreen(user)
case .signinCompleted:
print(".signinCompleted")
return NextFlowItems.end(withStepForParentFlow: MZStep.onboardingCompleted)
default:
return NextFlowItems.none
}
}
private func navigationToSigninScreen() -> NextFlowItems {
let viewModel = MCRxSignInViewModel(services)
let signinViewController = MCInputPhoneViewController.instantiate(viewModel)
self.rootViewController.pushViewController(signinViewController, animated: false)
return NextFlowItems.one(flowItem: NextFlowItem(nextPresentable: signinViewController, nextStepper: signinViewController))
}
private func navigationToSigninPasscodeScreen(_ user: AWSCognitoIdentityUser?) -> NextFlowItems {
let viewModel = MCRxPasscodeViewModel(services)
let passcodeViewController = MCValidatePhoneViewController.instantiate(viewModel, user: user)
self.rootViewController.pushViewController(passcodeViewController, animated: true)
return NextFlowItems.one(flowItem: NextFlowItem(nextPresentable: passcodeViewController, nextStepper: passcodeViewController))
}
}
struct OnboardingServices {
let authService: MZAuthService
init(_ parentService: AppServices) {
self.authService = MZAuthService()
}
}
MainFlow.swift
class MainFlow: Flow {
var root: Presentable {
return self.rootViewController
}
private lazy var rootViewController: UINavigationController = {
let viewController = UINavigationController()
return viewController
}()
private let services: AppServices
init(withServices services: AppServices) {
self.services = services
}
func navigate(to step: Step) -> NextFlowItems {
guard let step = step as? MZStep else { return NextFlowItems.none }
switch step {
case .main:
return navigateToMainScreen()
case .signOutCompleted:
print(".signOutCompleted")
return NextFlowItems.end(withStepForParentFlow: MZStep.mainCompleted)
default:
return NextFlowItems.none
}
}
private func navigateToMainScreen() -> NextFlowItems {
let mainViewController = MCMainViewController.instantiate()
self.rootViewController.pushViewController(mainViewController, animated: true)
return NextFlowItems.one(flowItem: NextFlowItem(nextPresentable: mainViewController, nextStepper: mainViewController))
}
}
Also, I noticed willNavigate is not triggered after NextFlowItems.end.
For example, if I signin
did navigate to flow=Demo.OnboardingFlow and step=signinCompleted
Reentrancy warning
did navigate to flow=Demo.AppFlow and step=onboardingCompleted
will navigate to flow=Demo.MainFlow and step=main
did navigate to flow=Demo.MainFlow and step=main
Not sure if I did something wrong.
Thanks
Hi!
I wanted to ask if child view controllers with their own flows are currently supported by RxFlow. If I understand the source code right, the FlowCoordinator won't be removed until it's parent was dismissed. I can think about cases where the parent won't be dismissed at all and we add/remove child view controllers continuously. That would lead to a situation with many FlowCoordinators lying around without a way of deleting them.
Please correct me if I'm wrong.
Is it just merely style?
from: SettingsFlow.swift
func navigate(to step: Step) -> NextFlowItems {
guard let step = step as? DemoStep else { return NextFlowItems.stepNotHandled }
switch step {
case .settings:
let navigationController = UINavigationController()
let settingsListViewController = SettingsListViewController.instantiate()
let settingsViewController = SettingsViewController.instantiate()
self.rootViewController.viewControllers = [navigationController, settingsViewController]
self.rootViewController.preferredDisplayMode = .allVisible
settingsViewController.title = "Api Key"
navigationController.viewControllers = [settingsListViewController]
if let navigationBarItem = navigationController.navigationBar.items?[0] {
let settingsButton = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.done,
target: self.settingsStepper,
action: #selector(SettingsStepper.settingsDone))
navigationBarItem.setRightBarButton(settingsButton, animated: false)
}
return NextFlowItems.multiple(flowItems: [NextFlowItem(nextPresentable: settingsListViewController, nextStepper: settingsListViewController),
NextFlowItem(nextPresentable: settingsViewController, nextStepper: settingsViewController)])
case .apiKey:
let settingsViewController = SettingsViewController.instantiate()
settingsViewController.title = "Api Key"
self.rootViewController.showDetailViewController(settingsViewController, sender: nil)
return NextFlowItems.one(flowItem: NextFlowItem(nextPresentable: settingsViewController, nextStepper: settingsViewController))
case .about:
let settingsAboutViewController = SettingsAboutViewController.instantiate()
settingsAboutViewController.title = "About"
self.rootViewController.showDetailViewController(settingsAboutViewController, sender: nil)
return NextFlowItems.one(flowItem: NextFlowItem(nextPresentable: settingsAboutViewController, nextStepper: settingsAboutViewController))
default:
return NextFlowItems.stepNotHandled
}
from: MainFlow.swift
func navigate(to step: Step) -> NextFlowItems {
guard let step = step as? DemoStep else { return NextFlowItems.stepNotHandled }
switch step {
case .apiKey:
return navigationToApiScreen()
case .apiKeyIsComplete:
return navigationToDashboardScreen()
default:
return NextFlowItems.stepNotHandled
}
}
It's pretty common that the user navigates back in a UINavigationController by gesture, so I'd like to know if you already support it?
Hi. In our project we are trying to handle network errors in viewModels
like that:
apiClient.getTask()
.asObservable()
.catchError{ (error) -> Observable<Task> in
guard let error = error as? MoyaError else { return }
guard case .statusCode(let response) = error else { return }
if response.statusCode == 401 {
print("\(response.statusCode) Unauthorized!")
invalidateUser()
self.step.accept(MySteps.auth)
}
}
In Flow we are using that code for pushing controller:
private func navigateToTask(withId identifier:String) -> NextFlowItems {
…
self.rootViewController.pushViewController(taskViewController, animated: true)
return NextFlowItems.one(flowItem: NextFlowItem(nextPresentable: taskViewController, nextStepper: taskViewModel))
}
So, error throws in the middle of animation of pushing view controller and step.accept()
did not work. On the other hand, when we are pushing VC without animation (self.rootViewController.pushViewController(taskViewController, animated: false)
) – its ok.
What are we doing wrong?
In the Demo app, all the services are being initialized in the AppDelegate and all the services are being passed to all the flows, even if it is not required by some flows. So, I just wanted to ask that what would be the best way to initialize and pass only the required services in a flow?
Thank you.
'Flowable'
looks like a protocol that describe some thing can flow
Hi, first of all thank you for your afford and time creating this interesting library. After playing around in RxFlowDemo I couldn't find a way how to "switch flows" without pushing them to the "MainFlow root".
I mean, in your app you have this structure:
UINavigationController (MainFlow)
--UIViewController(apiKey)
--UITabBarController (apiKeyIsComplete)
----UINavigationController(WishlistWarp)
----UINavigationController(WatchedFlow)
What I dont like is, that you have nested UINavigationControllers and you have UITabBarController inside UINavigationController and then you have UINavigationController for every tab. I don't think this is best practice (ScrollViewInsets are weird that way and acording to documentation you UITabBar is meant to be root).
I would rather create "APIFlow" with root (UINavigationContorller) and then I would have "DashboardFlow" with root (UITabBarController) and when "APIFlow" is finished I would like my UITabBarController to be as a root in my hierarchy. So i would like to somehow swap UIWindow's rootViewController from the flow's perspective.
Im not sure If I described my point well enough. If you will agree, I can help afterwards.
I really like the RxFlow
approach for separating navigation with flows.
Which works really well with navigating step by step as the user uses the app gets complicated with deep linking (e.g.: Navigating to ViewController after receiving a push notification).
How do I implement a global stepper for deep linking?
The Stepper
should do the following:
Step
Flow
Step
to the flowThanks for this library, coming at a great time.
(for instance when we only need to bootstrap a Flow with a first Step and don't want to code a basic Stepper for that)
Where is the documentation on coding a basic Stepper?
“Fatal error: Attempted to read an unowned reference but object 0x1c42d11e0 was already deallocated” is thrown when a child flow returns .end(withStepForParentFlow: step) on navigate(to:)
I have detected that the problem is caused by this line of code
self.delegate.endFlowCoordinator(withIdentifier: self.identifier)
In the following code snippet
The error is indicated in this lineWhat I understand is happening is that as you call
self.dismissFlow.onNext(Void ())
and immediately you call
self.delegate.endFlowCoordinator(withIdentifier: self.identifier)
The flow handled by the FlowCoordinator is deallocated in the latter so when the event is issued in dismissFlow the flow does not exist and therefore the error, the solution is to eliminate
122 self.delegate.endFlowCoordinator(withIdentifier: self.identifier)
Since it is also called in the subscription of dismissFlow
Apologies for my English
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.