-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
1 changed file
with
129 additions
and
72 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,111 +1,168 @@ | ||
[![Release](https://jitpack.io/v/0xZhangKe/KRouter.svg)](https://jitpack.io/#0xZhangKe/KRouter) | ||
![](https://img.shields.io/maven-central/v/io.github.0xzhangke/krouter-runtime) | ||
|
||
# KRouter | ||
Find interface implementation by uri string. | ||
# KRouter2 | ||
|
||
For Kotlin module to module communication. | ||
Due to time constraints, the first version of KRouter was hastily developed and only barely functional. Recently, I had some free time to redesign and refine its usage, adding support for Kotlin Multiplatform and parameter injection. The implementation was also completely revamped, replacing ServiceLoader with KSP for collecting route information. Additionally, it has been published to the Maven Central Repository. | ||
|
||
The main purpose is to open the compose screen from other modules(e.g. [Voyager](https://voyager.adriel.cafe/navigation)). | ||
## How to Use | ||
|
||
Base on ServiceLoader, KSP and Kotlin reflect. | ||
The usage remains as simple and straightforward as before. First, annotate the target class: | ||
|
||
# Usage | ||
Firstly, define a interface. | ||
```kotlin | ||
interface Screen | ||
@Destination("screen/main") | ||
class MainScreen( | ||
@RouteParam("id") val id: String, | ||
@RouteParam("name") val name: String, | ||
): Screen | ||
``` | ||
After that, define some implementation. | ||
|
||
Then, use `KRouter` to retrieve the corresponding class: | ||
|
||
```kotlin | ||
@Destination("screen/home") | ||
class HomeScreen(@Router val router: String = "") : Screen | ||
val screen = KRouter.route<Screen>("screen/main?name=zhangke&id=123") | ||
``` | ||
|
||
@Destination("screen/profile") | ||
class ProfileScreen : Screen { | ||
@Router | ||
lateinit var router: String | ||
} | ||
KRouter currently provides three annotations: `@Destination`, `@RouteUri`, and `@RouterParam`. | ||
|
||
### @Destination | ||
|
||
As the name suggests, the `@Destination` annotation marks the target class of a route, i.e., the destination, and takes a parameter as the route address. | ||
|
||
```kotlin | ||
@Target(AnnotationTarget.CLASS) | ||
@Retention(AnnotationRetention.RUNTIME) | ||
annotation class Destination(val route: String) | ||
``` | ||
This implementation can be distributed to any modules. | ||
|
||
Note that Destination class must have a No-Arg constructor. | ||
It can be used as follows: | ||
|
||
Now, you can route to any Screen by router. | ||
```kotlin | ||
val homeScreen = KRouter.route<Screen>("screen/home?name=zhangke") | ||
val profileScreen = KRouter.route<Screen>("screen/profile?name=zhangke") | ||
@Destination("screen/profile/detail") | ||
class ProfileDetailScreen: Screen | ||
``` | ||
As show above, you will get homeScreen, and it's router property is `screen/home?name=zhangke`. | ||
|
||
See the [sample.app](https://github.com/0xZhangKe/KRouter/tree/main/sample/app/src/main/java/com/zhangke/kouter/sample/app) module for a more detailed example. | ||
### @RouterParam | ||
|
||
## Integration | ||
Firstly, you must set up [KSP](https://kotlinlang.org/docs/ksp-overview.html) in your project. | ||
The `@RouterParam` annotation is used to mark route parameters. The fields marked by this annotation will be automatically injected with the corresponding route parameters. It also takes a parameter that represents the name of the query field in the route. KRouter will parse and assign values based on this field name. | ||
|
||
Then, add KRouter dependency in module. | ||
```kts | ||
// module's build.gradle.kts | ||
implementation("com.github.0xZhangKe.KRouter:core:0.1.6") | ||
ksp("com.github.0xZhangKe.KRouter:compiler:0.1.6") | ||
```kotlin | ||
@Target(AnnotationTarget.PROPERTY, AnnotationTarget.VALUE_PARAMETER) | ||
@Retention(AnnotationRetention.RUNTIME) | ||
annotation class RouteParam(val name: String) | ||
``` | ||
Additionally, add resources dir in source sets. | ||
```kts | ||
// module's build.gradle.kts | ||
kotlin { | ||
sourceSets.main { | ||
resources.srcDir("build/generated/ksp/main/resources") | ||
} | ||
|
||
Here’s an example of how to use it: | ||
|
||
```kotlin | ||
@Destination("screen/home/detail") | ||
class HomeDetailScreen( | ||
@RouteParam("id") val id: String, | ||
) : Screen { | ||
} | ||
``` | ||
|
||
In addition to constructor parameter injection, property field injection is also supported. | ||
|
||
```kotlin | ||
@Destination("screen/home/detail") | ||
class HomeDetailScreen( | ||
@RouteParam("id") val id: String, | ||
) : Screen { | ||
|
||
@RouteParam("title") var title: String? = null | ||
} | ||
``` | ||
|
||
## @Destination | ||
Destination annotation is defined for a route Destination. | ||
Currently, parameter types are limited to basic types and `String`. For more complex types, you can convert the object to a JSON string and encode it into the route. | ||
|
||
It's having two parameters: | ||
- `route`: This destination's identify route, must be uri string. | ||
- `type`: Which interface or abstract class this destination for, ignore this if just have single super type. | ||
<aside> | ||
💡 | ||
|
||
## @Router | ||
This annotation is used to identify which property is used to accept the route. | ||
Please note that KSP cannot access default values for parameters and properties at the moment. Therefore, injected fields cannot have default values. This means that if your injected field has a default value, and the route does not include that parameter, the default value will be ignored. | ||
|
||
So, this property must be a class's variable property or constructor parameter. | ||
</aside> | ||
|
||
The router is passed into this field when the destination object is constructed. | ||
### @RouteUri | ||
|
||
# Release | ||
Parameters or properties injected with this annotation will be assigned the full route. Since `@RouterParam` can only be used for field injection, for more complex parsing scenarios, you can use `@RouteUri` to retrieve the full route. | ||
|
||
# 0.2.3 | ||
- fixed increase compiler error | ||
### KRouterModule | ||
|
||
## 0.2.1 | ||
- upgrade to kotlin 1.9.22 | ||
`KRouterModule` is an interface used to implement specific routing capabilities. You can dynamically add custom modules through the `KRouter` class. By default, it will use the dynamically added modules first; if routing fails, it will fall back to KRouter's internal routing. | ||
|
||
Maybe you need add this code in your module: | ||
```kts | ||
tasks.withType<ProcessResources>{ | ||
duplicatesStrategy = DuplicatesStrategy.INCLUDE | ||
```kotlin | ||
interface KRouterModule { | ||
fun route(uri: String): Any? | ||
} | ||
``` | ||
|
||
## 0.1.6 | ||
- Supported multi router parameter in Destination | ||
- Supported kotlin increase compiler | ||
# 0.1.5 | ||
Firstly version. | ||
### Adding Dependencies | ||
|
||
KRouter provides two KSP plugins: | ||
|
||
# License | ||
- `krouter-collecting-compiler`: Collects route information, used in non-main modules. | ||
- `krouter-reducing-compiler`: Aggregates route information from various modules, only used in the main project module (app module). | ||
|
||
```kotlin | ||
// Used in non-main modules | ||
ksp("io.github.0xzhangke:krouter-collecting-compiler:$latest_version") | ||
// Used in the main module | ||
ksp("io.github.0xzhangke:krouter-reducing-compiler:$latest_version") | ||
``` | ||
|
||
Additionally, there is an annotation module and a runtime module: | ||
|
||
- `krouter-runtime`: The runtime module, providing `KRouter`, `KRouterModule`, and annotations. | ||
- `krouter-annotation`: The annotation module, containing only the annotations. | ||
|
||
```kotlin | ||
// For modules that only need to use annotations | ||
ksp("io.github.0xzhangke:krouter-runtime:$latest_version") | ||
// For modules that need routing capabilities | ||
ksp("io.github.0xzhangke:krouter-annotation:$latest_version") | ||
``` | ||
Copyright 2023 ZhangKe Contributors | ||
|
||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
## Implementation Details | ||
|
||
https://www.apache.org/licenses/LICENSE-2.0 | ||
First, the `collecting-compiler` plugin collects all route target class information in the module and generates a `KRouterModule` belonging to the current module. The generated class might look like this: | ||
|
||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
```kotlin | ||
public class RouterCollection_1726153189290() : KRouterModule { | ||
override fun route(uri: String): Any? { | ||
val routerUri = com.zhangke.krouter.internal.KRouterUri.create(uri) | ||
return when (routerUri.baseUrl) { | ||
"screen/home/detail" -> { | ||
com.zhangke.krouter.sample.home.HomeDetailScreen( | ||
id = routerUri.requireQuery("id"), | ||
) | ||
} | ||
"screen/home/landing" -> { | ||
com.zhangke.krouter.sample.home.HomeLandingScreen( | ||
router = uri, | ||
) | ||
} | ||
else -> null | ||
} | ||
} | ||
} | ||
``` | ||
|
||
All modules that depend on the `collecting-compiler` plugin will generate a class like this. | ||
|
||
Then, in the main project module (typically the app module), the `reducing-compiler` plugin is needed. This plugin generates a class with a fixed package name and class name, gathers all the classes generated by `collecting-compiler`, and adds them to the new class. | ||
|
||
```kotlin | ||
public class AutoReducingModule() : KRouterModule { | ||
|
||
private val moduleList: List<KRouterModule> = listOf<KRouterModule>( | ||
com.zhangke.krouter.generated.RouterCollection_1726153189283(), | ||
com.zhangke.krouter.generated.RouterCollection_1726153189290(), | ||
com.zhangke.krouter.generated.RouterCollection_1726153189284(), | ||
com.zhangke.krouter.generated.RouterCollection_1726153189709() | ||
) | ||
|
||
override fun route(uri: String): Any? = moduleList.firstNotNullOfOrNull { it.route(uri) } | ||
} | ||
``` | ||
|
||
At runtime, KRouter uses reflection to create this class, and the routing process is delegated to the implementation in this class. This completes the entire process of route collection and implementation. |