Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Type safe routes #50

Closed
denizdogan opened this issue Mar 1, 2022 · 4 comments
Closed

Type safe routes #50

denizdogan opened this issue Mar 1, 2022 · 4 comments

Comments

@denizdogan
Copy link
Contributor

Currently, I'm doing this:

enum AppRoutes {
    static let splash = "/"
    static let login = "/login"
    static let feed = "/feed"
}

SwitchRoutes {
    Route(AppRoutes.splash) {
        SplashScreenView()
    }
    Route(AppRoutes.feed) {
        FeedView()
    }
    ...
}

Is there any way to make this properly type safe, or at least safer? It seems rather fragile to rely on strings in this manner. Ideally, I wouldn't want to define my own "route enum" and have navigator.navigate accept only that type, etc.

@frzi
Copy link
Owner

frzi commented Mar 7, 2022

You could make an enum and extend the Route, NavLink and Navigator to accept this enum.

As an example:

enum AppRoute: String {
	case news
	case settings
	case login
}

extension Route where ValidatedData == RouteInformation {
	init(route: AppRoute, @ViewBuilder content: @escaping (ValidatedData) -> Content) {
		self.init(route.rawValue, content: content)
	}
}

extension Navigator {
	func navigate(_ route: AppRoute) {
		self.navigate(route.rawValue)
	}
}

extension NavLink {
	init(to route: AppRoute, replace: Bool = false, exact: Bool = false, @ViewBuilder content: @escaping (Bool) -> Content) {
		self.init(to: route.rawValue, replace: replace, exact: exact, content: content)
	}
}

navigator.navigate(to: .news)

You can now use type safe routes.

Things become a bit more complicated when you want to work with parameters/placeholders however. But with Swift's enum having support for associated values you could probably come up with some wicked things:

enum AppRoute {
	case news(id: UUID? = nil)
	case settings
	case login

	var path: String {
		switch self {
		case .news(let id): return "/news/\(id?.uuidString ?? "")"
		case .settings: return "/settings"
		case .login: return "/login"
		}
	}
}

navigator.navigate(to: .news(id: someUUID))

@denizdogan
Copy link
Contributor Author

@frzi Makes sense, thank you!

@denizdogan
Copy link
Contributor Author

@frzi I can't seem to figure out what the Route init would look like when using enum AppRoute with associated values on some of the cases. Am I right in suspecting that it's not simple?

@frzi
Copy link
Owner

frzi commented Mar 9, 2022

You'll probably want the paths for Routes to be formed differently. As an example:

enum AppRoute {
	case news(id: UUID? = nil)

	// Read this property in `NavLink` and `Navigator.navigate()`
	var path: String {
		switch self {
		case .news(let id): return "/news/\(id?.uuidString ?? "")"
		}
	}

	// Read this property in `Route`
	var route: String {
		switch self {
		case .news(let id): return "/news/\(id?.uuidString ?? ":id?")"
		}
	}
}

Route(.news()) { /* ... */ } // For `/news/:id?`
Route(.news(someConstantUUID)) { /* ... */ } // For `/news/edc54f97-ff00-463a-8897-f75144e41b7b` or something

With a bit of imagination I'm sure you'll be able to come up with a solution that fits your needs perfectly. 😄

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants