{"id":773,"url":"https://github.com/RxSwiftCommunity/RxFlow","last_synced_at":"2025-08-06T13:31:54.498Z","repository":{"id":28015276,"uuid":"115838237","full_name":"RxSwiftCommunity/RxFlow","owner":"RxSwiftCommunity","description":"RxFlow is a navigation framework for iOS applications based on a Reactive Flow Coordinator pattern","archived":false,"fork":false,"pushed_at":"2025-05-21T21:19:23.000Z","size":15038,"stargazers_count":1894,"open_issues_count":2,"forks_count":117,"subscribers_count":33,"default_branch":"main","last_synced_at":"2025-08-04T23:39:34.697Z","etag":null,"topics":["coordinator","flow","reactive-programming","rxswift","swift"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/RxSwiftCommunity.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2017-12-31T02:43:28.000Z","updated_at":"2025-07-29T15:37:33.000Z","dependencies_parsed_at":"2025-04-13T20:44:47.395Z","dependency_job_id":"ad6a853e-b5c2-4011-b286-1c16290cfae8","html_url":"https://github.com/RxSwiftCommunity/RxFlow","commit_stats":{"total_commits":221,"total_committers":31,"mean_commits":7.129032258064516,"dds":0.5429864253393666,"last_synced_commit":"93f95345e3fe1a73ea504f138b98116f57009783"},"previous_names":[],"tags_count":38,"template":false,"template_full_name":null,"purl":"pkg:github/RxSwiftCommunity/RxFlow","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RxSwiftCommunity%2FRxFlow","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RxSwiftCommunity%2FRxFlow/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RxSwiftCommunity%2FRxFlow/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RxSwiftCommunity%2FRxFlow/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/RxSwiftCommunity","download_url":"https://codeload.github.com/RxSwiftCommunity/RxFlow/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RxSwiftCommunity%2FRxFlow/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":268825945,"owners_count":24313268,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-05T02:00:12.334Z","response_time":2576,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["coordinator","flow","reactive-programming","rxswift","swift"],"created_at":"2024-01-05T20:15:31.013Z","updated_at":"2025-08-06T13:31:53.706Z","avatar_url":"https://github.com/RxSwiftCommunity.png","language":"Swift","readme":"| \u003cimg alt=\"RxFlow Logo\" src=\"https://raw.githubusercontent.com/RxSwiftCommunity/RxFlow/develop/Resources/RxFlow_logo.png\" width=\"250\"/\u003e | \u003cul align=\"left\"\u003e\u003cli\u003e\u003ca href=\"#about\"\u003eAbout\u003c/a\u003e\u003cli\u003e\u003ca href=\"#navigation-concerns\"\u003eNavigation concerns\u003c/a\u003e\u003cli\u003e\u003ca href=\"#rxflow-aims-to\"\u003eRxFlow aims to\u003c/a\u003e\u003cli\u003e\u003ca href=\"#installation\"\u003eInstallation\u003c/a\u003e\u003cli\u003e\u003ca href=\"#the-key-principles\"\u003eThe key principles\u003c/a\u003e\u003cli\u003e\u003ca href=\"#how-to-use-rxflow\"\u003eHow to use RxFlow\u003c/a\u003e\u003cli\u003e\u003ca href=\"#tools-and-dependencies\"\u003eTools and dependencies\u003c/a\u003e\u003c/ul\u003e |\n| -------------- | -------------- |\n| GitHub Actions | ![](https://github.com/RxSwiftCommunity/RxFlow/workflows/Tests/badge.svg) |\n| Frameworks | [![Carthage Compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) [![CocoaPods Compatible](https://img.shields.io/cocoapods/v/RxFlow.svg?style=flat)](http://cocoapods.org/pods/RxFlow) [![Swift Package Manager compatible](https://img.shields.io/badge/Swift%20Package%20Manager-compatible-brightgreen.svg)](https://github.com/apple/swift-package-manager) |\n| Platform | [![Platform](https://img.shields.io/cocoapods/p/RxFlow.svg?style=flat)](http://cocoapods.org/pods/RxFlow) |\n| Licence | [![License](https://img.shields.io/cocoapods/l/RxFlow.svg?style=flat)](http://cocoapods.org/pods/RxFlow) |\n\n\u003cspan style=\"float:none\" /\u003e\n\n# About\nRxFlow is a navigation framework for iOS applications based on a **Reactive Flow Coordinator pattern**.\n\nThis README is a short story of the whole conception process that led me to this framework.\n\nYou will find a very detail explanation of the whole project on my blog:\n- [RxFlow Part 1: In Theory](https://sideeffect.io/posts/2017-11-08-rxflow-part1/)\n- [RxFlow Part 2: In Practice](https://sideeffect.io/posts/2017-12-09-rxflow-part2/)\n- [RxFlow Part 3: Tips and Tricks](https://sideeffect.io/posts/2017-12-22-rxflow-part3/)\n\nThe Jazzy documentation can be seen here as well: [Documentation](http://community.rxswift.org/RxFlow/)\n\nAlso here is a [Reactive coordinators tech talk](https://youtu.be/b0aCv7rlKx4) which explain the goals and motivation of that framework. \nAvailable only in Russian. To get English subtitles you should press the *subtitles* button to see original (Russian) subtitles and then select Settings-\u003eSubtitles-\u003eTranslate-\u003e*choose_your_language*\n\n# Navigation concerns\nRegarding navigation within an iOS application, two choices are available:\n- Use the builtin mechanism provided by Apple and Xcode: storyboards and segues\n- Implement a custom mechanism directly in the code\n\nThe disadvantage of these two solutions:\n- Builtin mechanism: navigation is relatively static and the storyboards are massive. The navigation code pollutes the UIViewControllers\n- Custom mechanism: code can be difficult to set up and can be complex depending on the chosen design pattern (Router, Coordinator)\n\n# RxFlow aims to\n- Promote the cutting of storyboards into atomic units to enable collaboration and reusability of UIViewControllers\n- Allow the presentation of a UIViewController in different ways according to the navigation context\n- Ease the implementation of dependency injection\n- Remove every navigation mechanism from UIViewControllers\n- Promote reactive programming\n- Express the navigation in a declarative way while addressing the majority of the navigation cases\n- Facilitate the cutting of an application into logical blocks of navigation\n\n# Installation\n\n## Carthage\n\nIn your Cartfile:\n\n```ruby\ngithub \"RxSwiftCommunity/RxFlow\"\n```\n\n## CocoaPods\n\nIn your Podfile:\n\n```ruby\npod 'RxFlow'\n```\n\n## Swift Package Manager\n\nIn your Package.swift:\n\n```swift\nlet package = Package(\n  name: \"Example\",\n  dependencies: [\n    .package(url: \"https://github.com/RxSwiftCommunity/RxFlow.git\", from: \"2.10.0\")\n  ],\n  targets: [\n    .target(name: \"Example\", dependencies: [\"RxFlow\"])\n  ]\n)\n```\n\n# The key principles\n\nThe **Coordinator** pattern is a great way to organize the navigation within your application. It allows to:\n- Remove the navigation code from UIViewControllers.\n- Reuse UIViewControllers in different navigation contexts.\n- Ease the use of dependency injection.\n\nTo learn more about it, I suggest you take a look at this article: ([Coordinator Redux](http://khanlou.com/2015/10/coordinators-redux/)).\n\nNevertheless, the Coordinator pattern can have some drawbacks:\n- The coordination mechanism has to be written each time you bootstrap an application.\n- Communicating with the Coordinators stack can lead to a lot of boilerplate code.\n\nRxFlow is a reactive implementation of the Coordinator pattern. It has all the great features of this architecture, but brings some improvements:\n- It makes the navigation more declarative within **Flows**.\n- It provides a built-in **FlowCoordinator** that handles the navigation between **Flows**.\n- It uses reactive programming to trigger navigation actions towards the **FlowCoordinators**.\n\nThere are 6 terms you have to be familiar with to understand **RxFlow**:\n- **Flow**: each **Flow** defines a navigation area in your application. This is the place where you declare the navigation actions (such as presenting a UIViewController or another **Flow**).\n- **Step**: a **Step** is a way to express a state that can lead to a navigation. Combinations of **Flows** and **Steps** describe all the possible navigation actions. A **Step** can even embed inner values (such as Ids, URLs, ...) that will be propagated to screens declared in the **Flows**\n- **Stepper**: a **Stepper** can be anything that can emit **Steps** inside **Flows**.\n- **Presentable**: it is an abstraction of something that can be presented (basically **UIViewController** and **Flow** are **Presentable**).\n- **FlowContributor**: it is a simple data structure that tells the **FlowCoordinator** what will be the next things that can emit new **Steps** in a **Flow**.\n- **FlowCoordinator**: once the developer has defined the suitable combinations of **Flows** and **Steps** representing the navigation possibilities, the job of the **FlowCoordinator** is to mix these combinations to handle all the navigation of your app. **FlowCoordinators** are provided by **RxFlow**, you don't have to implement them.\n\n# How to use RxFlow\n\n## Code samples\n\n### How to declare **Steps**\n\n**Steps** are little pieces of states eventually expressing the intent to navigate, it is pretty convenient to declare them in a enum:\n\n```swift\nenum DemoStep: Step {\n    // Login\n    case loginIsRequired\n    case userIsLoggedIn\n\n    // Onboarding\n    case onboardingIsRequired\n    case onboardingIsComplete\n\n    // Home\n    case dashboardIsRequired\n\n    // Movies\n    case moviesAreRequired\n    case movieIsPicked (withId: Int)\n    case castIsPicked (withId: Int)\n\n    // Settings\n    case settingsAreRequired\n    case settingsAreComplete\n}\n```\n\nThe idea is to keep the **Steps** `navigation independent` as much as possible. For instance, calling a **Step** `showMovieDetail(withId: Int)` might be a bad idea since it tightly couples the fact of selecting a movie with the consequence of showing the movie detail screen. It is not up to the emitter of the **Step** to decide where to navigate, this decision belongs to the **Flow**. \n\n### How to declare a **Flow**\n\nThe following **Flow** is used as a Navigation stack. All you have to do is:\n- Declare a root **Presentable** on which your navigation will be based.\n- Implement the **navigate(to:)** function to transform a **Step** into a navigation actions.\n\n**Flows** can be used to implement dependency injection when instantiating the ViewControllers.\n\nThe **navigate(to:)** function returns a **FlowContributors**. This is how the next navigation actions will be produced. \n\nFor instance the value: ```.one(flowContributor: .contribute(withNextPresentable: viewController, withNextStepper: viewController.viewModel)``` means:\n- ```viewController``` is a **Presentable** and its lifecycle will affect the way the associated **Stepper** will emit **Steps**. For instance, if a **Stepper** emits a **Step** while its associated **Presentable** is temporarily hidden, this **Step** won't be taken care of.\n- ```viewController.viewModel``` is a **Stepper** and will contribute to the navigation in that **Flow** by emitting **Steps**, according to its associated **Presentable** lifecycle.\n\n```swift\nclass WatchedFlow: Flow {\n    var root: Presentable {\n        return self.rootViewController\n    }\n\n    private let rootViewController = UINavigationController()\n    private let services: AppServices\n\n    init(withServices services: AppServices) {\n        self.services = services\n    }\n\n    func navigate(to step: Step) -\u003e FlowContributors {\n\n        guard let step = step as? DemoStep else { return .none }\n\n        switch step {\n\n        case .moviesAreRequired:\n            return navigateToMovieListScreen()\n        case .movieIsPicked(let movieId):\n            return navigateToMovieDetailScreen(with: movieId)\n        case .castIsPicked(let castId):\n            return navigateToCastDetailScreen(with: castId)\n        default:\n            return .none\n        }\n    }\n\n    private func navigateToMovieListScreen() -\u003e FlowContributors {\n        let viewController = WatchedViewController.instantiate(withViewModel: WatchedViewModel(),\n                                                               andServices: self.services)\n        viewController.title = \"Watched\"\n\n        self.rootViewController.pushViewController(viewController, animated: true)\n        return .one(flowContributor: .contribute(withNextPresentable: viewController, withNextStepper: viewController.viewModel))\n    }\n\n    private func navigateToMovieDetailScreen (with movieId: Int) -\u003e FlowContributors {\n        let viewController = MovieDetailViewController.instantiate(withViewModel: MovieDetailViewModel(withMovieId: movieId),\n                                                                   andServices: self.services)\n        viewController.title = viewController.viewModel.title\n        self.rootViewController.pushViewController(viewController, animated: true)\n        return .one(flowContributor: .contribute(withNextPresentable: viewController, withNextStepper: viewController.viewModel))\n    }\n\n    private func navigateToCastDetailScreen (with castId: Int) -\u003e FlowContributors {\n        let viewController = CastDetailViewController.instantiate(withViewModel: CastDetailViewModel(withCastId: castId),\n                                                                  andServices: self.services)\n        viewController.title = viewController.viewModel.name\n        self.rootViewController.pushViewController(viewController, animated: true)\n        return .none\n    }\n}\n```\n\n### How to handle deep links\n\nFrom the AppDelegate you can reach the FlowCoordinator and call the `navigate(to:)` function when receiving a notification for instance.\n\nThe step passed to the function will then be passed to all the existing Flows so you can adapt the navigation.\n\n```swift\nfunc userNotificationCenter(_ center: UNUserNotificationCenter,\n                            didReceive response: UNNotificationResponse,\n                            withCompletionHandler completionHandler: @escaping () -\u003e Void) {\n    // example of how DeepLink can be handled\n    self.coordinator.navigate(to: DemoStep.movieIsPicked(withId: 23452))\n}\n```\n\n### How to adapt a Step before it triggers a navigation ?\n\nA Flow has a `adapt(step:) -\u003e Single\u003cStep\u003e` function that by default returns the step it has been given\nas a parameter.\n\nThis function is called by the FlowCoordinator before the `navigate(to:)` function. This is a perfect place\nto implement some logic that could for instance forbid a step to trigger a navigation. A common use case would be to handle the navigation permissions within an application.\n\nLet's say we have a PermissionManager:\n\n```swift\nfunc adapt(step: Step) -\u003e Single\u003cStep\u003e {\n    switch step {\n    case DemoStep.aboutIsRequired:\n        return PermissionManager.isAuthorized() ? .just(step) : .just(DemoStep.unauthorized)     \n    default:\n        return .just(step)         \n    }\n}\n\n...\n\nlater in the navigate(to:) function, the .unauthorized step could trigger an AlertViewController\n```\n\nWhy return a Single\u003cStep\u003e and not directly a Step ? Because some filtering processes could be asynchronous and need a user action to be performed (for instance a filtering based on the authentication layer of the device with TouchID or FaceID)\n\nIn order to improve the separation of concerns, a Flow could be injected with a delegate which purpose would be to handle the adaptions in the `adapt(step:)` function. The delegate could eventually be reused across multiple flows to ensure a consistency in the adaptations.\n\n### How to declare a **Stepper**\n\nIn theory a **Stepper**, as it is a protocol, can be anything (a UIViewController for instance) but a good practice is to isolate that behavior in a ViewModel or something similar.\n\nRxFlow comes with a predefined **OneStepper** class. For instance, it can be used when creating a new Flow to express the first **Step** that will drive the navigation.\n\nThe following **Stepper**  will emit a **DemoStep.moviePicked(withMovieId:)** each time the function **pick(movieId:)** is called. The WatchedFlow will then call the function **navigateToMovieDetailScreen (with movieId: Int)**.\n\n```swift\nclass WatchedViewModel: Stepper {\n\n    let movies: [MovieViewModel]\n    let steps = PublishRelay\u003cStep\u003e()\n\n    init(with service: MoviesService) {\n        // we can do some data refactoring in order to display things exactly the way we want (this is the aim of a ViewModel)\n        self.movies = service.watchedMovies().map({ (movie) -\u003e MovieViewModel in\n            return MovieViewModel(id: movie.id, title: movie.title, image: movie.image)\n        })\n    }\n\n    // when a movie is picked, a new Step is emitted.\n    // That will trigger a navigation action within the WatchedFlow\n    public func pick (movieId: Int) {\n        self.steps.accept(DemoStep.movieIsPicked(withId: movieId))\n    }\n\n}\n```\n\n### Is it possible to coordinate multiple Flows ?\n\nOf course, it is the aim of a Coordinator. Inside a Flow we can present UIViewControllers and also new Flows. The function **Flows.whenReady()** allows to be triggered when the new **Flow** is ready to be displayed and gives us back its root **Presentable**.\n\nFor instance, from the WishlistFlow, we launch the SettingsFlow in a popup.\n\n```swift\nprivate func navigateToSettings() -\u003e FlowContributors {\n\tlet settingsStepper = SettingsStepper()\n\tlet settingsFlow = SettingsFlow(withServices: self.services, andStepper: settingsStepper)\n\n    Flows.use(settingsFlow, when: .ready) { [unowned self] root in\n        self.rootViewController.present(root, animated: true)\n    }\n    \n    return .one(flowContributor: .contribute(withNextPresentable: settingsFlow, withNextStepper: settingsStepper))\n    }\n```\n\nThe `Flows.use(when:)` takes an `ExecuteStrategy` as a second parameter. It has two possible values:\n- .created: The completion block will be executed instantly\n- .ready: The completion block will be executed once the sub flows (SettingsFlow in the example) have emitted a first step\n\nFor more complex cases, see the **DashboardFlow.swift** and the **SettingsFlow.swift** files in which we handle a UITabBarController and a UISplitViewController.\n\n### How to bootstrap the RxFlow process\n\nThe coordination process is pretty straightforward and happens in the AppDelegate.\n\n```swift\nclass AppDelegate: UIResponder, UIApplicationDelegate {\n\n    let disposeBag = DisposeBag()\n    var window: UIWindow?\n    var coordinator = FlowCoordinator()\n    let appServices = AppServices()\n\n    func application(_ application: UIApplication,\n                     didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -\u003e Bool {\n\n        guard let window = self.window else { return false }\n\n        // listening for the coordination mechanism is not mandatory, but can be useful\n        coordinator.rx.didNavigate.subscribe(onNext: { (flow, step) in\n            print (\"did navigate to flow=\\(flow) and step=\\(step)\")\n        }).disposed(by: self.disposeBag)\n\n        let appFlow = AppFlow(withWindow: window, andServices: self.appServices)\n        self.coordinator.coordinate(flow: self.appFlow, with: AppStepper(withServices: self.appServices))\n\n        return true\n    }\n}\n```\n\nAs a bonus, **FlowCoordinator** offers a Rx extension that allows you to track the navigation actions (**FlowCoordinator.rx.willNavigate** and **FlowCoordinator.rx.didNavigate**).\n\n## Demo Application\nA demo application is provided to illustrate the core mechanisms. Pretty much every kind of navigation is addressed. The app consists of:\n- An AppFlow that represents the main navigation. This Flow will handle the OnboardingFlow and the DashboardFlow depending on the \"onboarding state\" of the user.\n- An OnBoardingFlow that represents a 2 steps onboarding wizard in a UINavigationController. It will only be displayed the first time the app is used.\n- A DashboardFlow that handles the Tabbar for the WishlistFlow and the WatchedFlow.\n- A WishlistFlow that represents a navigation stack of movies that you want to watch.\n- A WatchedFlow that represents a navigation stack of movies that you've already seen.\n- A SettingsFlow that represents the user's preferences in a master/detail presentation.\n\n\u003cbr/\u003e\n\u003ckbd\u003e\n\u003cimg style=\"border:2px solid black\" alt=\"Demo Application\" src=\"https://raw.githubusercontent.com/RxSwiftCommunity/RxFlow/develop/Resources/RxFlow.gif\"/\u003e\n\u003c/kbd\u003e\n\n# Tools and dependencies\n\nRxFlow relies on:\n- SwiftLint for static code analysis ([Github SwiftLint](https://github.com/realm/SwiftLint))\n- RxSwift to expose Steps as Observables the Coordinator can react to ([Github RxSwift](https://github.com/ReactiveX/RxSwift))\n- Reusable in the Demo App to ease the storyboard cutting into atomic ViewControllers ([Github Reusable](https://github.com/AliSoftware/Reusable))\n","funding_links":[],"categories":["App Routing","Libs","Uncategorized","Swift","Libraries","App Routing [🔝](#readme)"],"sub_categories":["App Routing","Uncategorized","Other free courses","Getting Started"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FRxSwiftCommunity%2FRxFlow","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FRxSwiftCommunity%2FRxFlow","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FRxSwiftCommunity%2FRxFlow/lists"}