Giter VIP home page Giter VIP logo

rxflow's People

Contributors

benjohnde avatar benmaer-parkwood avatar clsk avatar dangthaison91 avatar dchohfi avatar dimentar avatar dmanuelcl avatar edon2005 avatar freak4pc avatar funct7 avatar grafele avatar gruppio avatar james-lai-fpg avatar jordanekay avatar m0rtymerr avatar mashe avatar mgray88 avatar mtfum avatar mudox avatar onsissond avatar r-mckay avatar redryerye avatar ruslana003 avatar rynecheow avatar sebastianv1 avatar sirvon avatar sree127 avatar tomisacat avatar twittemb avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

rxflow's Issues

How could I implement a custom action for a UITabBarItem?

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?

Handling Deep Links

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?

Navigate to next item within a flow

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

Serious Memory Management Issues

A comment was left by @TobiasRe on this issue #25, about...

There are serious memory management issues with RxFlow to fix first.

Can anyone with production/ or app store release experience with this framework,
comment about any memory leaks they've discovered.

Thanks

UIAlertController Handling

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?

  • Do you create a general Step like "openAlert(title, message, actions)"? This could be implemented in all flows (or some kind of base class).
  • Do create a step per alert?
  • Do even create steps for alerts or do you just handle alerts in the controllers?
  • Do you have a different approach?

Thanks in advance!

Passing data throughout the Flow.

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.

navigate(to) fired multiple times

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))
    }
  
}

The best practice to handle nested flows

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?

RxReduce + RxFlow

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.

NoneStepper question

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.

Root window emit the completed event immediately after be shown

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

How to hide a tab bar when using RxFlow?

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?

Add willNavigate: Observable<Flow,Step> and didNavigate: Observable<Flow,Step> to Coordinator

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 😃

carthage installation issue

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

Always visible floating view and it's Steps

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.

Remove nested frameworks

The RxFlow framework should not contain nested frameworks!
At the latest during submitting to the AppStore, developers will prompted errors that RxFlow contains illegal nested frameworks.
To fix that disable the Copy Carthage Frameworks build phase for the framework.

bildschirmfoto 2018-02-22 um 13 30 54

bildschirmfoto 2018-02-22 um 13 42 18

potential memory leak?

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?

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.

Is Carthage mandatory?

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

Pattern for unit testing flows?

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?

Bundle returning target reference instead of module reference

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!

Few source of stepper...

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.

Passing needed dependencies as Step enum associated values

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.

Spice up Step enum.

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...

No such module RxFlow

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.

Type AppFlow does not conform to protocol Flow

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
2018-02-20 16 26 24

When I created a new empty project, linked pods, created my own Flow class like AppFlow, I also occured this error.

My environment:

  • Xcode 9.2
  • Swift 4

My Podfile:

use_frameworks!
target 'RxFlowDemo' do
  pod 'RxSwift'
  pod 'RxCocoa'
  pod 'RxFlow'
  pod 'Reusable'
end

What I'm doing wrong?

Temporary Flows which root which is neither presented nor pushed - how to dismiss?

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 UIViewControllers) 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 Presentables are presented or pushed.

How can I handle the scenario where I have several temporary Flows 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? :)

RxFlow stops working if a UIAlertController is presented by a flow's root

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
captura de pantalla 2018-03-20 a la s 6 18 07 p m
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

Flows.whenReady -> variable naming

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)
    }
}

`dismissed` observable triggered without view being dismissed

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.

Docs

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.

`Flows.whenReady` not work.

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.

Subscribe step

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.

Reentrancy anomaly was detected rxflow

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

Child View Controllers with own flows

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.

What's the difference in these two navigate(to:) impl?

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
        }
    }

self.step.accept() not working while [pushViewController animated:true]

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?

What is the need to initialize all the services in AppDelegate?

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.

RxFlowDemo nested NavigationControllers

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.

Global Stepper implementation

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:

  1. Accept a specific Step
  2. Select or create and attach the corresponding Flow
  3. Add the Stepto the flow

Other Types of Steppers Impl

Thanks 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?

flow deallocation when return .end on navigate(to:)

“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

captura de pantalla 2018-03-24 a la s 5 43 06 p m

The error is indicated in this line

captura de pantalla 2018-03-24 a la s 5 41 36 p m

What 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
captura de pantalla 2018-03-24 a la s 5 41 36 p m

Apologies for my English

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.