Githawk: [VISION] Extended use of Routable architecture

Created on 3 Nov 2018  路  6Comments  路  Source: GitHawkApp/GitHawk

Laying out my vision for a more unified architecture for app navigation and view-controller isolation.

  • [x] All navigation is done through Routables. e.g. within a view controller:
func onTap() {
  let route = RepoRoute(owner: "GitHawkApp", name: "GitHawk")
  self.router.handle(route)
}
  • [x] Use ObjC runtime to attach Router to all view controllers

    • Requires an API change since pushing is all done in RoutePerformable extensions atm

    • Router does the attaching when VCs tell it to handle a route

  • [ ] Lint against navigationController.pushViewController(...) or showDetailViewController(...)
  • [x] Router communicates RoutePerformables to its delegate (AppController) which can then inject shared app state & objects into view controllers
  • [ ] Client renamed to NetworkClient
  • [ ] GitHubClient renamed to AppSession
  • [ ] VCs _protocolize_ their client usage instead of depending on and using NetworkClient

    • e.g. MilestoneViewController would take:

protocol MilestoneViewControllerNetworkClient {
  fetch(owner: String, repo: String, completion: @escaping (Result<Something>) -> Void)
}

The router + client protocol design enables us to _use and test_ the VC in isolation by removing dependencies that need to be daisy-chained to other VCs.

  • [ ] Write XCUITests on individual view controller's w/ _individually_ stubbed client protocols
  • [ ] Always viewController?.view.endEditing(false) to resign keyboard

Hopefully this makes sense. It will take some grinding to get there, but I think we'll have a really solid app architecture design in the end.


Related: there's been a lot of chatter of making other "hawk-like" apps. It would be really cool to create all of this shared app architecture and infra can be shared (maybe even a template base app) to create more apps.

馃幆 project management

Most helpful comment

Next up is building routes for all navigation! Exciting b/c this will start to remove all VC dependencies and get us a step towards launching/testing VCs in isolation. Onward!

All 6 comments

@heshamsalman this plan likely conflicts w/ #2368

lebron

More hawks in the store! Sounds exciting.

Sent with GitHawk

This would solve my block in #2247. That would be an easy, low-stakes issue to test this out on - intercept a regex pattern with owner/repo info and route to the Repo in-app.

Router Plan

  • Subclass Router from NSObject
  • Create objc runtime method to attach router to VC
  • Temporary router method to accept a VC and handle the navigation

    • Push, show, showDetail

    • Allow to slowly start creating routes

Navigation push

History/UIViewController+HistoryAction.swift:
  self?.navigationController?.pushViewController(
Issues/Review/IssueReviewSectionController.swift:
  viewController?.navigationController?.pushViewController(controller, animated: trueUnlessReduceMotionEnabled)
Notifications/NotificationsViewController.swift:
  navigationController?.pushViewController(controller, animated: trueUnlessReduceMotionEnabled)
Notifications/NotificationsViewController.swift:
  navigationController?.pushViewController(controller, animated: trueUnlessReduceMotionEnabled)
Repository/RepositoryCodeDirectoryViewController.swift:
  navigationController?.pushViewController(controller, animated: trueUnlessReduceMotionEnabled)
Repository/RepositoryCodeDirectoryViewController.swift:
  navigationController?.pushViewController(controller, animated: trueUnlessReduceMotionEnabled)
Repository/RepositorySummarySectionController.swift:
  self.viewController?.navigationController?.pushViewController(
Systems/AppRouter/Router.swift:
  from.navigationController?.pushViewController(
View Controllers/UIViewController+PresentLabels.swift:
  navigationController?.pushViewController(
Views/IssueTextActionsView+Markdown.swift:
  viewController?.navigationController?.pushViewController(controller, animated: trueUnlessReduceMotionEnabled)
Views/TextActionsController.swift:
  picker.pushViewController(uploadController, animated: trueUnlessReduceMotionEnabled)

Show

Issues/Files/IssueFilesSectionController.swift:
  self?.viewController?.show(controller, sender: nil)
Issues/Files/IssueViewFilesSectionController.swift:
  viewController?.show(controller, sender: nil)
Issues/Referenced/IssueReferencedSectionController.swift:
  viewController?.show(controller, sender: nil)
Issues/Review/IssueReviewSectionController.swift:
  viewController?.show(IssuesViewController(client: client, model: issue), sender: nil)
Repository/RepositoryViewController.swift:
  show(issuesViewController, sender: self)
View Controllers/SplitViewControllerDelegate.swift:
  tab.selectedViewController?.show(first, sender: sender)
View Controllers/SplitViewControllerDelegate.swift:
  tab.selectedViewController?.show(vc, sender: sender)

Show Detail

Bookmark/BookmarkViewController.swift:
  showDetailViewController(navigation, sender: nil)
Issues/IssuesViewController.swift:
  showDetailViewController(controller, sender: nil)
Notifications/NotificationSectionController.swift:
  viewController?.showDetailViewController(navigation, sender: nil)
Search/SearchResultSectionController.swift:
  viewController?.showDetailViewController(navigation, sender: nil)
Search/SearchViewController.swift:
  showDetailViewController(navigation, sender: nil)
Settings/SettingsViewController.swift:
  self.navigationController?.showDetailViewController(navigationController, sender: self)
Settings/SettingsViewController.swift:
  showDetailViewController(navController, sender: self)
Settings/SettingsViewController.swift:
  showDetailViewController(navController, sender: self)
Settings/SettingsViewController.swift:
  showDetailViewController(navigation, sender: nil)
Systems/AppRouter/IssueRoute+RoutePerformable.swift:
  splitViewController.showDetailViewController(controller, sender: nil)
Systems/AppRouter/RepoRoute+RoutePerformable.swift:
  splitViewController.showDetailViewController(controller, sender: nil)

Present (optional)

Bookmark/BookmarkViewController.swift:
  present(alert, animated: true)
Image Upload/ImageUploadTableViewController.swift:
  present(alert, animated: trueUnlessReduceMotionEnabled)
Image Upload/ImageUploadTableViewController.swift:
  present(previewViewController, animated: trueUnlessReduceMotionEnabled)
Issues/Comments/IssueCommentSectionController.swift:
  self?.viewController?.present(alert, animated: trueUnlessReduceMotionEnabled)
Issues/Comments/IssueCommentSectionController.swift:
  self?.viewController?.present(nav, animated: trueUnlessReduceMotionEnabled)
Issues/Comments/IssueCommentSectionController.swift:
  self?.viewController?.present(
Issues/Comments/IssueCommentSectionController.swift:
  viewController?.present(alert, animated: trueUnlessReduceMotionEnabled)
Issues/EditComment/EditCommentViewController.swift:
  present(alert, animated: trueUnlessReduceMotionEnabled)
Issues/IssuesViewController.swift:
  present(activityController, animated: trueUnlessReduceMotionEnabled)
Issues/IssuesViewController.swift:
  present(alert, animated: trueUnlessReduceMotionEnabled)
Issues/Managing/IssueManagingSectionController.swift:    func present(controller: UIViewController, from cell: UICollectionViewCell) {
Issues/Managing/IssueManagingSectionController.swift:
  present(controller: newLabelsController(), from: cell)
Issues/Managing/IssueManagingSectionController.swift:
  present(controller: controller, from: cell)
Issues/Managing/IssueManagingSectionController.swift:
  present(controller: controller, from: cell)
Issues/Managing/IssueManagingSectionController.swift:
  present(controller: controller, from: cell)
Issues/Merge/IssueMergeSectionController.swift:
  viewController?.present(alert, animated: trueUnlessReduceMotionEnabled)
Issues/Merge/IssueMergeSectionController.swift:
  viewController?.present(alert, animated: trueUnlessReduceMotionEnabled)
Login/LoginSplashViewController.swift:
  present(alert, animated: trueUnlessReduceMotionEnabled)
Login/LoginSplashViewController.swift:
  present(alert, animated: trueUnlessReduceMotionEnabled)
Notifications/NotificationSectionController.swift:
  viewController?.present(alert, animated: trueUnlessReduceMotionEnabled)
Notifications/NotificationsViewController.swift:
  present(alert, animated: trueUnlessReduceMotionEnabled)
Notifications/NotificationsViewController.swift:
  present(alert, animated: trueUnlessReduceMotionEnabled)
Repository/RepositoryCodeBlobViewController.swift:
  present(alert, animated: trueUnlessReduceMotionEnabled)
Repository/RepositoryCodeDirectoryViewController.swift:
  present(alert, animated: trueUnlessReduceMotionEnabled)
Repository/RepositoryViewController.swift:
  present(alert, animated: trueUnlessReduceMotionEnabled)
Repository/RepositoryViewController.swift:
  present(alert, animated: trueUnlessReduceMotionEnabled)
Search/SearchViewController.swift:
  present(alert, animated: trueUnlessReduceMotionEnabled)
Settings/SettingsAccountsViewController.swift:
  present(alert, animated: trueUnlessReduceMotionEnabled)
Settings/SettingsAccountsViewController.swift:
  present(alert, animated: trueUnlessReduceMotionEnabled)
Settings/SettingsViewController.swift:
  present(navController, animated: trueUnlessReduceMotionEnabled)
Settings/SettingsViewController.swift:
  present(alert, animated: trueUnlessReduceMotionEnabled)
Systems/AppRouter/AppController.swift:
  self.splitViewController.present(controller, animated: animated)
Systems/AppRouter/AppController.swift:
  present()
Systems/PhotoViewHandler.swift:
  viewController?.present(photosViewController, animated: trueUnlessReduceMotionEnabled)
Systems/PhotoViewHandler.swift:
  self?.viewController?.present(photosViewController, animated: trueUnlessReduceMotionEnabled)
Utility/AlertAction.swift:
  self.rootViewController?.present(activityController, animated: trueUnlessReduceMotionEnabled)
Utility/AlertAction.swift:
  self.rootViewController?.present(nav, animated: trueUnlessReduceMotionEnabled)
View Controllers/UIViewController+Alerts.swift:
  present(alert, animated: trueUnlessReduceMotionEnabled)
View Controllers/UIViewController+CancelAction.swift:
  present(alert, animated: trueUnlessReduceMotionEnabled)
View Controllers/UIViewController+FilePathTitle.swift:
  present(alert, animated: trueUnlessReduceMotionEnabled)
View Controllers/UIViewController+Safari.swift:   present(safariViewController, animated: trueUnlessReduceMotionEnabled)
View Controllers/UIViewController+Safari.swift:
  present(controller, animated: trueUnlessReduceMotionEnabled)
Views/MarkdownStyledTextView.swift:
  .present(activityController, animated: trueUnlessReduceMotionEnabled)
Views/TextActionsController.swift:
  viewController?.present(imagePicker, animated: trueUnlessReduceMotionEnabled)

Next up is building routes for all navigation! Exciting b/c this will start to remove all VC dependencies and get us a step towards launching/testing VCs in isolation. Onward!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

BasThomas picture BasThomas  路  3Comments

BasThomas picture BasThomas  路  3Comments

Iron-Ham picture Iron-Ham  路  3Comments

weyert picture weyert  路  3Comments

BasThomas picture BasThomas  路  3Comments