Как передать объект в навигации в Jetpack Compose?

Из документации я могу передавать строку, целое число и т. Д. Но как я могу передавать объекты при навигации?

Примечание. Если я установил тип аргумента parcelable, приложение выйдет из строя с java.lang.UnsupportedOperationException: Parcelables don't support default values..

      composable(
    "vendor/details/{vendor}",
        arguments = listOf(navArgument("vendor") {
            type = NavType.ParcelableType(Vendor::class.java)
        })
) {
// ...
}

7 ответов

Следующие обходные пути основаны на navigation-compose версия 1.0.0-alpha10.


Я нашел 2 обходных пути для передачи объектов.

1. Преобразуйте объект в строку JSON:

Здесь мы можем передавать объекты, используя строковое представление объекта JSON.

Пример кода:

      val ROUTE_USER_DETAILS = "user-details/{user}"


// Pass data (I am using Moshi here)
val user = User(id = 1, name = "John Doe") // User is a data class.

val moshi = Moshi.Builder().build()
val jsonAdapter = moshi.adapter(User::class.java).lenient()
val userJson = jsonAdapter.toJson(user)

navController.navigate(
    ROUTE_USER_DETAILS.replace("{user}", userJson)
)


// Receive Data
NavHost {
    composable(ROUTE_USER_DETAILS) { backStackEntry ->
        val userJson =  backStackEntry.arguments?.getString("user")
        val moshi = Moshi.Builder().build()
        val jsonAdapter = moshi.adapter(User::class.java).lenient()
        val userObject = jsonAdapter.fromJson(userJson)

        UserDetailsView(userObject) // Here UserDetailsView is a composable.
    }
}


// Composable function/view
@Composable
fun UserDetailsView(
    user: User
){
    // ...
}

2. Передача объекта с помощью NavBackStackEntry:

Здесь мы можем передавать данные, используя navController.currentBackStackEntry и получать данные, используя navController.previousBackStackEntry.

Пример кода:

      val ROUTE_USER_DETAILS = "user-details/{user}"


// Pass data
val user = User(id = 1, name = "John Doe") // User is a parcelable data class.

navController.currentBackStackEntry?.arguments?.putParcelable("user", user)
navController.navigate(ROUTE_USER_DETAILS)


// Receive data
NavHost {
    composable(ROUTE_USER_DETAILS) { backStackEntry ->
        val userObject = navController.previousBackStackEntry?.arguments?.getParcelable<User>("user")
        
        UserDetailsView(userObject) // Here UserDetailsView is a composable.
    }
}


// Composable function/view
@Composable
fun UserDetailsView(
    user: User
){
    // ...
}

Важное примечание : второе решение не сработает, если мы откроем назад стопки при навигации.

С аргументами:

Вы можете просто сделать этот объектSerializableи передать его вbackStackEntryаргументы, также вы можете передатьString,Longи т. д :

      data class User (val name:String) : java.io.Serializable

val user = User("Bob")

navController.currentBackStackEntry?.arguments?.apply {
        putString("your_key", "key value")
        putSerializable("USER", user)
      )
}

чтобы получить значение из аргументов, вам нужно сделать следующее:

      navController.previousBackStackEntry?.arguments?.customGetSerializable("USER")

код дляcustomGetSerializableфункция:

      @Suppress("DEPRECATION")
inline fun <reified T : Serializable> Bundle.customGetSerializable(key: String): T? {
    return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) getSerializable(key, T::class.java)
    else getSerializable(key) as? T
}

С сохраненным статехандле

Иногда у вас есть аргументы, допускающие значение NULL, поэтому вы можете использоватьsavedStateHandle:

      appState.navController.currentBackStackEntry?.savedStateHandle?.set("USER", user)

и получить значение:

      navController.previousBackStackEntry?.savedStateHandle?.get("USER")

Parcelables в настоящее время не поддерживает значения по умолчанию, поэтому вам нужно передать свой объект как строковое значение. Да, это обходной путь ... Поэтому вместо передачи самого объекта как объекта Parcelize мы можем превратить этот объект в JSON (String) и передать его через навигацию, а затем проанализировать этот JSON обратно в объект в пункте назначения. Вы можете использовать GSON для преобразования объекта в строку json ...

Json в объект

      fun <A> String.fromJson(type: Class<A>): A {
    return Gson().fromJson(this, type)
}

Объект в строку Json

      fun <A> A.toJson(): String? {
    return Gson().toJson(this)
}

Пользователь NavType.StringType вместо NavType.ParcelableType..

      composable("detail/{item}",
            arguments = listOf(navArgument("item") {
                type = NavType.StringType
            })
        ) {
            it.arguments?.getString("item")?.let { jsonString ->
                val user = jsonString.fromJson(User::class.java)
                DetailScreen( navController = navController, user = user )
            }
          }

Теперь перейдите, передав строку ..

        val userString = user.toJson()
  navController.navigate(detail/$userString")

Как правило, не рекомендуется передавать объекты в системе навигации Jetpack Compose. Вместо этого лучше передать идентификатор данных и получить доступ к этим данным из репозитория.

Но если вы хотите пойти по этому пути, я бы рекомендовал использовать CBOR вместо JSON. Это короче, и вы можете передать все, включая URL-адреса. Сериализация Kotlin поддерживает это.

      @Serializable
data class Vendor(
  val url: String,
  val name: String,
  val timestmap: Long
)

val vendor = Vendor(...)
val serializedVendor = Cbor.encodeToHexString(vendor)

Для больших объектов не забудьте позвонитьCbor.encodeToHexString(vendor)наDispatchers.Defaultвместо блокировки основного потока.

Позвольте мне дать вам очень простые ответы. У нас есть разные варианты, например.

  1. Использование аргументов , но проблема в том, что вы не можете совместно использовать длинные или сложные объекты, только простые типы, такие какInt,Stringи т. д. Теперь вы думаете о преобразовании объектов вJsonStringи пытаюсь пройти его, но этот трюк работает только для маленьких или легких объектов. Исключение выглядит так:

    java.lang.IllegalArgumentException: пункт назначения навигации, соответствующий запросу NavDeepLinkRequest{ uri="VERY LONG OBJECT STRING" }, не может быть найден в графе навигации NavGraph(0x0) startDestination={Destination(0x2e9fc7db) route=Screen_A}

  2. Теперь у нас есть Parsable Type в navArgument , но нам нужно поместить этот объект в текущий и получить его со следующего экрана. Проблема с этим решением заключается в том, что вам нужно сохранить этот экран в файле . Вы не можетеPopOutтвойbackStack. Например, если вы хотите вывести экран входа в систему при переходе на главный экран, вы не сможете получить объект с экрана входа на главный экран.

  3. Вам нужно создать SharedViewModel. Убедитесь, что вы используете только общее состояние, и используйте этот метод только в том случае, если два предыдущих вам не подходят.

      @HiltViewModel
class JobViewModel : ViewModel() {

 var jobs by mutableStateOf<Job?>(null)
   private set

fun allJob(job:Job)
{
    Toast.makeText(context,"addJob ${job.companyName}", Toast.LENGTH_LONG).show()
    jobs=job
}




 @Composable
fun HomeNavGraph(navController: NavHostController,
 ) {
val jobViewModel:JobViewModel= viewModel() // note:- same jobViewModel pass 
    in argument because instance should be same , otherwise value will null
val context = LocalContext.current
NavHost(
    navController = navController,
    startDestination = NavigationItems.Jobs.route
) {
    composable(
        route = NavigationItems.Jobs.route
    ) {
        JobsScreen(navController,jobViewModel)
    }

    composable(
        route= NavigationItems.JobDescriptionScreen.route
    )
    {
        JobDescriptionScreen(jobViewModel=jobViewModel)
    }

}
}

}

 in function argument (jobViewModel: JobViewModel)
   items(lazyJobItems) {

            job -> Surface(modifier = Modifier.clickable {
                if (job != null) {
                    jobViewModel.allJob(job=job)
                    navController.navigate(NavigationItems.JobDescriptionScreen.route)
                }

используйте это расширение для передачи пакета:

      fun NavController.navigate(
    route: String,
    args: Bundle,
    navOptions: NavOptions? = null,
    navigatorExtras: Navigator.Extras? = null,
) {
    graph
        .findNode(route)
        ?.id
        ?.let { nodeId ->
            navigate(
                resId = nodeId,
                args = args,
                navOptions = navOptions,
                navigatorExtras = navigatorExtras
            )
        }
        ?: error("route $route not found")
}

тогда просто возьми это какbackStackEntry.arguments?.getParcelable(KEY)

Другие вопросы по тегам