Skip to content

Commit

Permalink
#91 navigateByDeepLink 개선, arguments를 전달받지 않은 경우에 빈 객체를 받도록 개선
Browse files Browse the repository at this point in the history
  • Loading branch information
pknujsp committed Jun 1, 2023
1 parent b1e113b commit 9d130af
Show file tree
Hide file tree
Showing 6 changed files with 139 additions and 30 deletions.
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
package com.android.mediproject.core.common.util

import android.net.Uri
import android.os.Bundle
import androidx.annotation.MainThread
import androidx.core.net.toUri
import androidx.fragment.app.Fragment
import androidx.navigation.NavArgsLazy
import androidx.navigation.NavArgument
import androidx.navigation.NavController
import androidx.navigation.NavDeepLink
import androidx.navigation.NavDeepLinkRequest
import androidx.navigation.NavOptions
import androidx.navigation.NavType
import com.android.mediproject.core.model.local.navargs.BaseNavArgs

Expand All @@ -16,7 +22,7 @@ import com.android.mediproject.core.model.local.navargs.BaseNavArgs
* @param parameter Uri에 들어갈 파라미터
* @return Uri
*/
private fun toDeepUrl(deepLinkUrl: String, parameter: Map<String, Any?>): Uri = StringBuilder(deepLinkUrl).let { uri ->
private fun toQueryUri(deepLinkUrl: String, parameter: Map<String, Any?>): Uri = StringBuilder(deepLinkUrl).let { uri ->
parameter.takeIf {
it.isNotEmpty()
}?.also { map ->
Expand All @@ -31,6 +37,29 @@ private fun toDeepUrl(deepLinkUrl: String, parameter: Map<String, Any?>): Uri =
uri.toString().toUri()
}

/**
* Uri Builder
*
* 쿼리가 key만 포함된 Uri를 생성하는 함수입니다.
*
* @param parameter Uri에 들어갈 파라미터
* @return Uri
*/
private fun toBaseUri(deepLinkUrl: String, parameter: Map<String, Any?>): Uri = StringBuilder(deepLinkUrl).let { uri ->
parameter.takeIf {
it.isNotEmpty()
}?.also { map ->
uri.append("?")
map.onEachIndexed { index, entry ->
if (entry.value != null) {
uri.append("${entry.key}={${entry.key}}")
if (index != map.size - 1) uri.append('&')
}
}
}
uri.toString().toUri()
}

/**
* NavController를 확장하는 함수입니다.
*
Expand All @@ -39,19 +68,63 @@ private fun toDeepUrl(deepLinkUrl: String, parameter: Map<String, Any?>): Uri =
* @param deepLinkUrl DeepLink Url
* @param parameter DeepLink에 들어갈 파라미터
*/
fun NavController.navigateByDeepLink(deepLinkUrl: String, parameter: BaseNavArgs) {
fun NavController.navigateByDeepLink(
deepLinkUrl: String, parameter: BaseNavArgs, navOptions: NavOptions? = null) {
val parameterMap = parameter.toMap()
toDeepUrl(deepLinkUrl, parameterMap).also { finalUri ->
toQueryUri(deepLinkUrl, parameterMap).also { finalUri ->
graph.matchDeepLink(NavDeepLinkRequest(finalUri, null, null))?.also { deepLinkMatch ->
parameterMap.takeIf {
it.isNotEmpty()
}?.forEach { (key, value) ->
deepLinkMatch.destination.addArgument(
key, NavArgument.Builder().setType(NavType.StringType).setIsNullable(false).setDefaultValue(value).build()
)

value ?: return@forEach

val navType: NavType<out Any?> = when (value) {
is String -> NavType.StringType
is Int -> NavType.IntType
is Long -> NavType.LongType
is Float -> NavType.FloatType
is Boolean -> NavType.BoolType
else -> NavType.ReferenceType
}

deepLinkMatch.destination.addArgument(key,
NavArgument.Builder().setType(navType).setIsNullable(true).setDefaultValue(value).build())
}
}
}

this.navigate(deepLinkUrl.toUri())
}
this.navigate(deepLinkUrl.toUri(), navOptions)
}


fun NavController.deepNavigate(
deepLinkUrl: String, parameter: BaseNavArgs, navOptions: NavOptions? = null) {
val parameterMap = parameter.toMap()

// "medilenz://main?name={name}&age={age}"
val baseUri = toBaseUri(deepLinkUrl, parameterMap)
// "medilenz://main?name=NAME&age=AGE"
val finalUri = toQueryUri(deepLinkUrl, parameterMap)

graph.matchDeepLink(NavDeepLinkRequest(baseUri, null, null))?.apply {
// finalUri를 도착지 그래프에 새로운 딥링크로 추가
if (!destination.arguments.contains(NAV_KEY)) {
destination.addArgument(NAV_KEY, NAV_ARG)
destination.addDeepLink(NavDeepLink.Builder().setUriPattern(baseUri.toString()).build())
}
}

navigate(finalUri, navOptions)
}

@MainThread
inline fun <reified Args : BaseNavArgs> Fragment.navArgs(): NavArgsLazy<out Args> = NavArgsLazy(Args::class) {
val bundle = arguments ?: Bundle()
bundle.apply {
putString("className", Args::class.java.name)
}
}

const val NAV_KEY = "__Deep_Args_"
private val NAV_ARG = NavArgument.Builder().setType(NavType.BoolType).setDefaultValue(true).build()
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ import androidx.navigation.NavArgs
import kotlin.reflect.KClass
import kotlin.reflect.full.memberProperties
import kotlin.reflect.full.primaryConstructor
import kotlin.reflect.full.starProjectedType
import kotlin.reflect.javaType

abstract class BaseNavArgs(
val className: String
) : NavArgs {
val className: String) : NavArgs {
fun toBundle(): Bundle {
val result = Bundle()
toMap().forEach { (key, value) ->
Expand All @@ -25,10 +25,27 @@ abstract class BaseNavArgs(
return result
}

fun toBundle(map: Map<String, Any?>): Bundle {
val result = Bundle()
map.forEach { (key, value) ->
when (value) {
null -> return@forEach
is String -> result.putString(key, value)
is Int -> result.putInt(key, value)
is Long -> result.putLong(key, value)
is Float -> result.putFloat(key, value)
is Boolean -> result.putBoolean(key, value)
}
}
return result
}

companion object {
@OptIn(ExperimentalStdlibApi::class)
@JvmStatic
fun fromBundle(bundle: Bundle): BaseNavArgs {
if (bundle.containsKey("className") && bundle.size() == 1) return empty(bundle.getString("className")!!)

val kClass: KClass<BaseNavArgs> = Class.forName(bundle.getString("className")!!).kotlin as KClass<BaseNavArgs>
bundle.classLoader = kClass.java.classLoader

Expand All @@ -42,12 +59,33 @@ abstract class BaseNavArgs(
Long::class.java -> bundle.getLong(parameter.name)
Float::class.java -> bundle.getFloat(parameter.name)
Boolean::class.java -> bundle.getBoolean(parameter.name)
else -> throw IllegalArgumentException("Argument \"${parameter.name}\" is marked as non-null but was passed a null value.")
else -> {}
}
}
}
return constructor.call(*args.toTypedArray())
}

@JvmStatic
private fun empty(className: String): BaseNavArgs {
val kClass: KClass<BaseNavArgs> = Class.forName(className).kotlin as KClass<BaseNavArgs>

val constructor = kClass.primaryConstructor!!

val args: List<Any> = constructor.parameters.map { kProperty1 ->
when (kProperty1.type) {
String::class.starProjectedType -> ""
Int::class.starProjectedType -> 0
Long::class.starProjectedType -> 0L
Float::class.starProjectedType -> 0f
Boolean::class.starProjectedType -> false
else -> false
}
}


return constructor.call(*args.toTypedArray())
}
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import com.android.mediproject.core.network.onResponse
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf
import javax.inject.Inject

class RecallSuspensionDataSourceImpl @Inject constructor(
Expand All @@ -30,7 +29,7 @@ class RecallSuspensionDataSourceImpl @Inject constructor(
}, onFailure = {
Result.failure(it)
}).also {
flowOf(it)
emit(it)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,18 @@ package com.android.mediproject.feature.news
import android.os.Bundle
import android.view.View
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.navArgs
import com.android.mediproject.core.common.util.navArgs
import com.android.mediproject.core.model.local.navargs.RecallDisposalArgs
import com.android.mediproject.core.ui.base.BaseFragment
import com.android.mediproject.feature.news.databinding.FragmentNewsBinding
import dagger.hilt.android.AndroidEntryPoint

@AndroidEntryPoint
class NewsFragment :
BaseFragment<FragmentNewsBinding, NewsViewModel>(FragmentNewsBinding::inflate) {
class NewsFragment : BaseFragment<FragmentNewsBinding, NewsViewModel>(FragmentNewsBinding::inflate) {

override val fragmentViewModel: NewsViewModel by viewModels()

private val recallDisposalArgs: RecallDisposalArgs by lazy {
try {
navArgs<RecallDisposalArgs>().value
} catch (e: Exception) {
RecallDisposalArgs("")
}
}
private val recallDisposalArgs by navArgs<RecallDisposalArgs>()

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
Expand All @@ -31,5 +24,7 @@ class NewsFragment :
NewsNavHost(arguments = recallDisposalArgs)
}
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ fun NewsNavHost(
""
}

val start = if (arguments.product.isNotEmpty()) "detailRecallSuspension"
val start = if (arguments.product.isNotEmpty()) "detailRecallSuspension/{product}"
else "news"

NavHost(navController = navController, startDestination = start) {
Expand All @@ -68,8 +68,10 @@ fun NewsNavHost(
}
}
composable(
"detailRecallSuspension/{product}",
arguments = listOf(navArgument("product") { type = NavType.StringType })
"detailRecallSuspension/{product}", arguments = listOf(navArgument("product") {
type = NavType.StringType
defaultValue = arguments.product
})
) {
DetailRecallDisposalScreen()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ package com.android.mediproject.feature.penalties.recentpenaltylist

import android.os.Bundle
import android.view.View
import androidx.core.net.toUri
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
import com.android.mediproject.core.common.util.deepNavigate
import com.android.mediproject.core.common.util.navigateByDeepLink
import com.android.mediproject.core.common.viewmodel.UiState
import com.android.mediproject.core.model.local.navargs.RecallDisposalArgs
Expand Down Expand Up @@ -57,8 +57,8 @@ class RecentPenaltyListFragment :

uiState.data.forEach { itemDto ->
itemDto.onClick = {
findNavController().navigateByDeepLink(
"medilens://main/news_nav", RecallDisposalArgs(it.product)
findNavController().deepNavigate(
"medilens://main/news_nav", RecallDisposalArgs(it.product),
)
}
}
Expand All @@ -82,7 +82,9 @@ class RecentPenaltyListFragment :
binding.headerView.setOnExpandClickListener {}

binding.headerView.setOnMoreClickListener {
findNavController().navigate("medilens://main/news_nav".toUri())
findNavController().navigateByDeepLink(
"medilens://main/news_nav", RecallDisposalArgs(""),
)
}
}
}

0 comments on commit 9d130af

Please sign in to comment.