/Navigator

Multi-module 에서 Navigator/DeepLink 을 사용하여 화면이동하는 기능

Primary LanguageKotlin

🚧🚧🚧 WIP 🚧🚧🚧

Navigator

Navigator 라이브러리는 멀티 모듈 간의 화면 이동을 유연하게 정의하고 사용하기 위한 목적으로 만들어졌습니다.

Target

현재 화면 이동은 다음 Android Component를 지원합니다.

  • Activity/Fragment to Activity
  • Fragment (TBD)

Architecture

Multi module sample

project
│
├──app
│
├──RoutesConst : define navigation routes
│
├── features
│   ├──featureHome
│   │  └──Sample : Navigator
│   ├──feature1
│   │  └──Sample : Navigator, DeepLink, Graph
│   └──feature2
│      └──Sample : Navigator, DeepLink
│
└──core-android : Utility

Define Configuration

1. Config

  • DeepLink Base Scheme
import com.pluu.navigator.Navigator
import com.pluu.navigator.NavigatorController

// Define Config
val config = NavigatorController.Config(
    baseScheme = "pluu"
)
Navigator.registerConfig(config)

2. Define Direction

Define navigation directions

import com.pluu.navigator.Direction
import com.pluu.navigator.DirectionParam
import com.pluu.navigator.DirectionWithParam

object SampleDirection {
    object Feature1 : Direction()
    object Feature2 : DirectionWithParam<SampleParam>()
}

class SampleParam(val value: Int) : DirectionParam()

Register Pattern#1 : Provider Interface

import com.pluu.navigator.provider.Provider

// Step1. Define Route
class SampleProvider : Provider {
    override fun provide() {
        SampleDirection.Feature1.register { starter ->
            Intent(starter.context, SampleActivity::class.java)
        }
        // more ...
    }
}

// Step2. Register route
val sampleProvider: Provider = /** Provider */
sampleProvider.provide()

Register Pattern#2 : Functional

// Simple funtion provider
import com.pluu.navigator.provider.routeProvider

// Step1. Define Route
val sampleProvider = routeProvider(SampleDirection.Feature1) { starter ->
    Intent(starter.context, SampleActivity::class.java)
}

// Step2. Register route
sampleProvider.provide()

Using direction ~ Navigator

Simple startActivity

Navigator.of(/** Activity or Fragment */)
   .start(/** Direction */)

Navigator.of(/** Activity or Fragment */)
   .start(direction = /** Direction */, param = /** param */)

Simple startActivityForResult

Navigator.of(/** Activity or Fragment */)
   .startForResult(
      direction = /** Direction */, 
      requestCode = /** Request Code */
   )

Navigator.of(/** Activity or Fragment */)
   .startForResult(
      direction = /** Direction */, 
      param = /** param */, 
   		requestCode = /** Request Code */
   )

Simple startActivityForResult ActivityResultLauncher

Navigator.of(/** Activity or Fragment */)
   .startForResult(
      direction = /** Direction */, 
      launcher = /** ActivityResultLauncher Launcher */
   )

Navigator.of(/** Activity or Fragment */)
   .startForResult(
      direction = /** Direction */, 
      param = /** param */, 
      launcher = /** ActivityResultLauncher Launcher */
   )

Using direction ~ Received parameter

Navigator로 전달된 데이터는 DIRECTION_PARAMS_KEY 를 키로 하여 Serializable 로 Intent에 담아서 꺼냅니다.

package com.pluu.navigator

const val DIRECTION_PARAMS_KEY = "DIRECTION_PARAMS_KEY"

Simple Function

  • getter 함수를 가지는 bindExtra는 DIRECTION_PARAMS_KEY를 키로 하여 넘겨진 Serializable 데이터에서 특정 필드를 가져올 때 유용한 함수입니다.
NonNull Return
bindExtra O
optionalBindExtra X
bindExtra<T, R>(getter: (T) -> R) O
optionalBindExtra<T, G>(getter: (T) -> R) X
class SampleActivity : AppCompatActivity(R.layout.activity_sample) {
    
    private val param1 by bindExtra</**Getter Type*/>()  
    private val param2 by optionalBindExtra</**Getter Type*/>()
    private val param3 by bindExtra</**Root Type*/, /**Getter Type*/> {
			// do return action
    }
    private val param4 by optionalBindExtra</**Root Type*/, /**Getter Type*/> {
			// do return action
    }
}

3. Define DeepLink

딥 링크의 파라미터 값은 다음과 같이 생성됩니다.

Register Pattern#1 : Provider Interface

// Provider Interface
import com.pluu.navigator.DeepLink
import com.pluu.navigator.provider.Provider

// Step1. Define DeepLink
class SampleProvider : Provider {
    override fun provide() {
        // Simple
        DeepLink("pluu://feature1").register { starter, result ->
            val intent = Intent(starter.context, SampleActivity::class.java)
            starter.start(intent)
        }
      
        // Base Scheme + Path
        DeepLink("feature1?type={type}").register { starter, result ->
            // Sample : pluu://feature1?type=123
            // result.args
            // +------+-------+
            // | Key  | Value |
            // +------+-------+
            // | type | 123   |
            // +------+-------+                                                       
            val intent = Intent(starter.context, SampleActivity::class.java)
            starter.start(intent)
        }
    }
}

// Step2. Register route
val sampleProvider: Provider = /** Provider */
sampleProvider.provide()

Register Pattern#2 : Functional

Default pattern

import com.pluu.navigator.provider.deepLinkProvider

// Step1. Define DeepLink

// Provider
val DeepLink_Simple: Provider = deepLinkProvider("pluu://feature1") { starter, result ->
    val intent = Intent(starter.context, SampleActivity::class.java)
    starter.start(intent)
}

// Provider : Base Scheme + Path
val DeepLink_Relative_Path: Provider = deepLinkProvider("feature1/sample1?type={type}") { starter, result ->
    // Sample : pluu://feature1/sample1?type=123
    // result.args
    // +------+-------+
    // | Key  | Value |
    // +------+-------+
    // | type | 123   |
    // +------+-------+
    val intent = Intent(starter.context, SampleActivity::class.java)
    starter.start(intent)
}

// Step2. Register route
val sampleProvider: Provider = /** Provider */
sampleProvider.provide()

Register Pattern#3 : Command

  • Instance the Command object using Gson

Provider Interface

import com.pluu.navigator.DeepLinkCommand
import com.pluu.navigator.DeepLink
import com.pluu.navigator.provider.Provider
import com.pluu.navigator.starter.Starter

// Step1. Define DeepLink

// Provider 
class SampleCommandProvider : Provider {
    override fun provide() {
        DeepLink("pluu://feature2/command?type={type}").register(SampleCommand::class.java)
    }
}

class SampleCommand(
    private val type: Int
) : DeepLinkCommand {
    override fun execute(starter: Starter) {
        val intent = Intent(starter.context, SampleActivity::class.java)
        starter.start(intent)
    }
}

// Step2. Register route
val sampleProvider: Provider = /** Provider */
sampleProvider.provide()

Functional

import com.pluu.navigator.DeepLinkCommand
import com.pluu.navigator.provider.deepLinkProvider
import com.pluu.navigator.provider.Provider
import com.pluu.navigator.starter.Starter

// Step1. Define DeepLink

// Provider : Command
val DeepLink_Command: Provider = deepLinkProvider<SampleCommand>("pluu://feature1/sample2?type={type}")

class SampleCommand(
    private val type: Int
) : DeepLinkCommand {
    override fun execute(starter: Starter) {
        val intent = Intent(starter.context, SampleActivity::class.java)
        starter.start(intent)
    }
}

// Step2. Register route
val sampleProvider: Provider = /** Provider */
sampleProvider.provide()

4. Define Graph

Register Pattern#1 : Builder Pattern

import com.pluu.navigator.DeepLinkConfig
import com.pluu.navigator.Navigator
import com.pluu.navigator.RouteGraph

// Step1. Define graph
val sampleGraph: RouteGraph.Builder = RouteGraph.Builder(
    graphName = "feature1",
    deepLinkConfig = DeepLinkConfig("feature1")
).apply {   
    // Add Route
    addDestination(/** */) { starter ->
        Intent(starter.context, SampleActivity::class.java)
    }
  
    // Add DeepLink
    addDeepLink(/** */) { starter, result -> 
        val intent = Intent(starter.context, SampleActivity::class.java)
        starter.start(intent)
    }
}

// Step2. Register graph
Navigator.addDestinations(sampleGraph.build())

Register Pattern#2 : Functional

import com.pluu.navigator.DeepLinkConfig
import com.pluu.navigator.Navigator
import com.pluu.navigator.routeGraph

// Step1. Define Graph
val sampleGraph: RouteGraph = routeGraph(
    graphName = "sample",
    deepLinkConfig = DeepLinkConfig("feature1") // prefix path
) {
    addDestination(Routes1.Feature1_Graph) { starter ->
        Intent(starter.context, SampleActivity::class.java)
    }

    // URL : pluu://feature1 
    // Base Scheme + DeepLink-config Prefix Path + Path
    addDeepLink("/") { starter, result -> 
        val intent = Intent(starter.context, SampleActivity::class.java)
        starter.start(intent)
    }

    // URL : pluu://feature1/1
    // Base Scheme + DeepLink-config Prefix Path + Path
    addDeepLink("1") { starter, result ->
        val intent = Intent(starter.context, SampleActivity::class.java)
        starter.start(intent)
    }

    // URL : luckystar://izumi/konata
    addDeepLink("luckystar://izumi/konata") { starter, result -> 
        val intent = Intent(starter.context, SampleActivity::class.java)
        starter.start(intent)
    }
}

// Step2. Register graph
Navigator.addDestinations(sampleGraph)

Using deeplink

Navigator.of(this)
   .execute("pluu://feature1/sample1?type=123")

5. Extension

DeepLink#register

import com.pluu.navigator.util.register

class DeepLinkCommandProvider : Provider {
    override fun provide() {
        DeepLink("/** DeepLink */").register<SampleCommand>()
    }
}

RouteGraph.Builder#addDeepLink

import com.pluu.navigator.util.addDeepLink

val sampleGraph = routeGraph(
    /** define graph */
) {
    addDeepLink<SampleCommand>("/** DeepLink */")
}

99. Etc

Reference