پیمایش با نوشتن

مؤلفه Navigation از برنامه های Jetpack Compose پشتیبانی می کند. می‌توانید در حالی که از زیرساخت‌ها و ویژگی‌های مؤلفه ناوبری بهره می‌برید، بین اجزای سازنده حرکت کنید.

راه اندازی

برای پشتیبانی از Compose، از وابستگی زیر در فایل build.gradle ماژول برنامه خود استفاده کنید:

شیار

dependencies {
    def nav_version = "2.8.2"

    implementation "androidx.navigation:navigation-compose:$nav_version"
}

کاتلین

dependencies {
    val nav_version = "2.8.2"

    implementation("androidx.navigation:navigation-compose:$nav_version")
}

شروع کنید

هنگام پیاده‌سازی ناوبری در یک برنامه، یک میزبان ناوبری، نمودار و کنترل‌کننده را اجرا کنید. برای اطلاعات بیشتر، به نمای کلی ناوبری مراجعه کنید.

برای اطلاعات در مورد نحوه ایجاد NavController در Compose، به بخش Compose در Create a Navigation Controller مراجعه کنید.

NavHost ایجاد کنید

برای اطلاعات در مورد نحوه ایجاد NavHost در Compose، به بخش Compose در Design your navigation graph مراجعه کنید.

برای اطلاعات در مورد پیمایش به یک Composable، به پیمایش به مقصد در مستندات معماری مراجعه کنید.

برای اطلاعات در مورد انتقال آرگومان‌ها بین مقصدهای قابل ترکیب، به بخش نوشتن در نمودار پیمایش خود را طراحی کنید .

هنگام ناوبری داده های پیچیده را بازیابی کنید

اکیداً توصیه می‌شود که هنگام پیمایش، اشیاء داده پیچیده را رد نکنید، بلکه در عوض حداقل اطلاعات لازم مانند شناسه منحصربه‌فرد یا شکل دیگری از شناسه را به عنوان آرگومان در هنگام انجام اقدامات ناوبری ارسال کنید:

// Pass only the user ID when navigating to a new destination as argument
navController.navigate(Profile(id = "user1234"))

اشیاء پیچیده باید به عنوان داده در یک منبع منفرد از حقیقت، مانند لایه داده، ذخیره شوند. هنگامی که پس از پیمایش در مقصد خود فرود آمدید، سپس می توانید با استفاده از شناسه ارسال شده، اطلاعات مورد نیاز را از منبع منفرد حقیقت بارگیری کنید. برای بازیابی آرگومان های ViewModel خود که مسئول دسترسی به لایه داده است، از SavedStateHandle ViewModel استفاده کنید:

class UserViewModel(
    savedStateHandle: SavedStateHandle,
    private val userInfoRepository: UserInfoRepository
) : ViewModel() {

    private val profile = savedStateHandle.toRoute<Profile>()

    // Fetch the relevant user information from the data layer,
    // ie. userInfoRepository, based on the passed userId argument
    private val userInfo: Flow<UserInfo> = userInfoRepository.getUserInfo(profile.id)

// …

}

این رویکرد به جلوگیری از از دست رفتن داده ها در طول تغییرات پیکربندی و هرگونه ناهماهنگی زمانی که شی مورد نظر در حال به روز رسانی یا جهش است، کمک می کند.

برای توضیح عمیق‌تر در مورد اینکه چرا باید از ارسال داده‌های پیچیده به‌عنوان آرگومان اجتناب کنید، و همچنین فهرستی از انواع آرگومان‌های پشتیبانی‌شده، به انتقال داده‌ها بین مقصدها مراجعه کنید.

Navigation Compose از پیوندهای عمیقی پشتیبانی می کند که می توانند به عنوان بخشی از تابع composable() نیز تعریف شوند. پارامتر deepLinks آن لیستی از اشیاء NavDeepLink را می پذیرد که می توانند به سرعت با استفاده از متد navDeepLink() ایجاد شوند:

@Serializable data class Profile(val id: String)
val uri = "https://www.example.com"

composable<Profile>(
  deepLinks = listOf(
    navDeepLink<Profile>(basePath = "$uri/profile")
  )
) { backStackEntry ->
  ProfileScreen(id = backStackEntry.toRoute<Profile>().id)
}

این پیوندهای عمیق به شما این امکان را می دهند که یک URL خاص، اکشن یا نوع mime را با یک Composable مرتبط کنید. به طور پیش فرض، این پیوندهای عمیق در معرض برنامه های خارجی قرار نمی گیرند. برای در دسترس قرار دادن این پیوندهای عمیق به صورت خارجی، باید عناصر <intent-filter> مناسب را به فایل manifest.xml برنامه خود اضافه کنید. برای فعال کردن پیوند عمیق در مثال قبل، باید موارد زیر را در عنصر <activity> مانیفست اضافه کنید:

<activity …>
  <intent-filter>
    ...
    <data android:scheme="https" android:host="www.example.com" />
  </intent-filter>
</activity>

هنگامی که پیوند عمیق توسط برنامه دیگری راه اندازی می شود، پیمایش به طور خودکار به آن پیوندهای عمیق متصل می شود.

همین پیوندهای عمیق همچنین می توانند برای ساختن یک PendingIntent با پیوند عمیق مناسب از یک composable استفاده شوند:

val id = "exampleId"
val context = LocalContext.current
val deepLinkIntent = Intent(
    Intent.ACTION_VIEW,
    "https://www.example.com/profile/$id".toUri(),
    context,
    MyActivity::class.java
)

val deepLinkPendingIntent: PendingIntent? = TaskStackBuilder.create(context).run {
    addNextIntentWithParentStack(deepLinkIntent)
    getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT)
}

سپس می توانید از این deepLinkPendingIntent مانند هر PendingIntent دیگری برای باز کردن برنامه خود در مقصد پیوند عمیق استفاده کنید.

ناوبری تودرتو

برای اطلاعات در مورد نحوه ایجاد نمودارهای پیمایش تو در تو، به نمودارهای تودرتو مراجعه کنید.

ادغام با نوار ناوبری پایین

با تعریف NavController در یک سطح بالاتر در سلسله مراتب قابل ترکیب، می توانید Navigation را با اجزای دیگر مانند مؤلفه ناوبری پایین متصل کنید. انجام این کار به شما امکان می دهد با انتخاب آیکون های نوار پایین حرکت کنید.

برای استفاده از اجزای BottomNavigation و BottomNavigationItem ، وابستگی androidx.compose.material را به برنامه Android خود اضافه کنید.

شیار

dependencies {
    implementation "androidx.compose.material:material:1.7.5"
}

android {
    buildFeatures {
        compose true
    }

    composeOptions {
        kotlinCompilerExtensionVersion = "1.5.15"
    }

    kotlinOptions {
        jvmTarget = "1.8"
    }
}

کاتلین

dependencies {
    implementation("androidx.compose.material:material:1.7.5")
}

android {
    buildFeatures {
        compose = true
    }

    composeOptions {
        kotlinCompilerExtensionVersion = "1.5.15"
    }

    kotlinOptions {
        jvmTarget = "1.8"
    }
}

برای پیوند دادن موارد موجود در نوار پیمایش پایین به مسیرها در نمودار ناوبری، توصیه می‌شود کلاسی مانند TopLevelRoute که در اینجا مشاهده می‌شود، تعریف کنید که دارای یک کلاس مسیر و یک نماد باشد.

data class TopLevelRoute<T : Any>(val name: String, val route: T, val icon: ImageVector)

سپس آن مسیرها را در لیستی قرار دهید که توسط BottomNavigationItem قابل استفاده است:

val topLevelRoutes = listOf(
   TopLevelRoute("Profile", Profile, Icons.Profile),
   TopLevelRoute("Friends", Friends, Icons.Friends)
)

در BottomNavigation composable خود، NavBackStackEntry فعلی را با استفاده از تابع currentBackStackEntryAsState() دریافت کنید. این ورودی به شما امکان دسترسی به NavDestination فعلی را می دهد. سپس وضعیت انتخاب شده هر BottomNavigationItem را می توان با مقایسه مسیر مورد با مسیر مقصد فعلی و مقصدهای اصلی آن برای رسیدگی به مواردی که از ناوبری تودرتو با استفاده از سلسله مراتب NavDestination استفاده می کنید، تعیین کرد.

مسیر مورد نیز برای اتصال onClick lambda به یک تماس برای navigate استفاده می‌شود تا با ضربه زدن روی مورد به آن مورد هدایت شود. با استفاده از پرچم‌های saveState و restoreState ، حالت و پشته پشته آن آیتم به درستی ذخیره و بازیابی می‌شود، زیرا بین موارد پیمایش پایینی جابه‌جا می‌شوید.

val navController = rememberNavController()
Scaffold(
  bottomBar = {
    BottomNavigation {
      val navBackStackEntry by navController.currentBackStackEntryAsState()
      val currentDestination = navBackStackEntry?.destination
      topLevelRoutes.forEach { topLevelRoute ->
        BottomNavigationItem(
          icon = { Icon(topLevelRoute.icon, contentDescription = topLevelRoute.name) },
          label = { Text(topLevelRoute.name) },
          selected = currentDestination?.hierarchy?.any { it.hasRoute(topLevelRoute.route::class) } == true,
          onClick = {
            navController.navigate(topLevelRoute.route) {
              // Pop up to the start destination of the graph to
              // avoid building up a large stack of destinations
              // on the back stack as users select items
              popUpTo(navController.graph.findStartDestination().id) {
                saveState = true
              }
              // Avoid multiple copies of the same destination when
              // reselecting the same item
              launchSingleTop = true
              // Restore state when reselecting a previously selected item
              restoreState = true
            }
          }
        )
      }
    }
  }
) { innerPadding ->
  NavHost(navController, startDestination = Profile, Modifier.padding(innerPadding)) {
    composable<Profile> { ProfileScreen(...) }
    composable<Friends> { FriendsScreen(...) }
  }
}

در اینجا شما از روش NavController.currentBackStackEntryAsState() استفاده می کنید تا حالت navController را از تابع NavHost خارج کنید و آن را با مؤلفه BottomNavigation به اشتراک بگذارید. این بدان معناست که BottomNavigation به طور خودکار به روزترین حالت را دارد.

قابلیت همکاری

اگر می خواهید از مؤلفه Navigation با Compose استفاده کنید، دو گزینه دارید:

  • یک نمودار ناوبری با مولفه Navigation برای قطعات تعریف کنید.
  • یک نمودار ناوبری را با NavHost در Compose با استفاده از مقصدهای Compose تعریف کنید. این تنها در صورتی امکان پذیر است که تمام صفحه های موجود در نمودار ناوبری قابل ترکیب باشند.

بنابراین، توصیه برای برنامه های ترکیبی Compose و Views استفاده از مولفه Navigation مبتنی بر Fragment است. سپس بخش‌ها صفحه‌های مبتنی بر View، صفحه‌های Compose و صفحه‌هایی را که از Views و Compose استفاده می‌کنند، نگه می‌دارند. هنگامی که محتویات هر قطعه در Compose قرار گرفت، گام بعدی این است که همه آن صفحه‌ها را با Navigation Compose به هم گره بزنید و همه قطعات را حذف کنید.

برای تغییر مقاصد در کد Compose، رویدادهایی را که می‌توانند به هر composable در سلسله‌مراتب منتقل شوند و توسط آن‌ها راه‌اندازی شوند، نمایش می‌دهید:

@Composable
fun MyScreen(onNavigate: (Int) -> Unit) {
    Button(onClick = { onNavigate(R.id.nav_profile) } { /* ... */ }
}

در قطعه خود، با یافتن NavController و پیمایش به مقصد، پلی بین Compose و مولفه Navigation مبتنی بر قطعه ایجاد می‌کنید:

override fun onCreateView( /* ... */ ) {
    setContent {
        MyScreen(onNavigate = { dest -> findNavController().navigate(dest) })
    }
}

از طرف دیگر، می‌توانید NavController به سلسله مراتب Compose خود منتقل کنید. با این حال، نمایش توابع ساده بسیار قابل استفاده مجدد و آزمایش پذیرتر است.

تست کردن

کد پیمایش را از مقاصد قابل ترکیب خود جدا کنید تا آزمایش هر یک از قابلیت‌ها را به صورت مجزا، جدا از NavHost قابل ترکیب، فعال کنید.

این به این معنی است که شما نباید navController مستقیماً به هیچ composable منتقل کنید و در عوض تماس های ناوبری را به عنوان پارامتر ارسال کنید. این اجازه می دهد تا همه اجزای سازنده شما به صورت جداگانه قابل آزمایش باشند، زیرا در آزمایشات به نمونه ای از navController نیاز ندارند.

سطح غیرمستقیم ارائه شده توسط لامبدای composable ، چیزی است که به شما امکان می دهد کد پیمایش خود را از خود ترکیب بندی جدا کنید. این در دو جهت کار می کند:

  • فقط آرگومان های تجزیه شده را به composable خود منتقل کنید
  • لامبداهایی را که باید توسط composable برای پیمایش به جای خود NavController فعال شوند، عبور دهید.

به عنوان مثال، یک ProfileScreen قابل ترکیب که یک userId به عنوان ورودی دریافت می کند و به کاربران اجازه می دهد به صفحه نمایه یک دوست حرکت کنند، ممکن است دارای امضای زیر باشد:

@Composable
fun ProfileScreen(
    userId: String,
    navigateToFriendProfile: (friendUserId: String) -> Unit
) {
 …
}

به این ترتیب، Composable ProfileScreen به طور مستقل از Navigation کار می کند و به آن اجازه می دهد مستقل آزمایش شود. لامبدای composable حداقل منطق مورد نیاز برای پر کردن شکاف بین API های ناوبری و قابل ترکیب شما را در بر می گیرد:

@Serializable data class Profile(id: String)

composable<Profile> { backStackEntry ->
    val profile = backStackEntry.toRoute<Profile>()
    ProfileScreen(userId = profile.id) { friendUserId ->
        navController.navigate(route = Profile(id = friendUserId))
    }
}

توصیه می‌شود آزمایش‌هایی بنویسید که نیازمندی‌های پیمایش برنامه شما را با آزمایش NavHost ، اقدامات پیمایشی که به composable‌های شما و همچنین تک تک قطعات صفحه نمایش شما ارسال می‌شوند، بنویسید.

تست NavHost

برای شروع آزمایش NavHost خود، وابستگی آزمایش پیمایش زیر را اضافه کنید:

dependencies {
// ...
  androidTestImplementation "androidx.navigation:navigation-testing:$navigationVersion"
  // ...
}

NavHost برنامه خود را در یک Composable بپیچید که NavHostController را به عنوان پارامتر می پذیرد.

@Composable
fun AppNavHost(navController: NavHostController){
  NavHost(navController = navController){ ... }
}

اکنون می توانید AppNavHost و تمام منطق ناوبری تعریف شده در NavHost را با ارسال نمونه ای از مصنوع آزمایش ناوبری TestNavHostController آزمایش کنید. یک تست UI که مقصد شروع برنامه و NavHost شما را تأیید می کند به این صورت است:

class NavigationTest {

    @get:Rule
    val composeTestRule = createComposeRule()
    lateinit var navController: TestNavHostController

    @Before
    fun setupAppNavHost() {
        composeTestRule.setContent {
            navController = TestNavHostController(LocalContext.current)
            navController.navigatorProvider.addNavigator(ComposeNavigator())
            AppNavHost(navController = navController)
        }
    }

    // Unit test
    @Test
    fun appNavHost_verifyStartDestination() {
        composeTestRule
            .onNodeWithContentDescription("Start Screen")
            .assertIsDisplayed()
    }
}

آزمایش اقدامات ناوبری

می‌توانید پیاده‌سازی ناوبری خود را به روش‌های مختلف، با کلیک روی عناصر رابط کاربری و سپس تأیید مقصد نمایش داده شده یا با مقایسه مسیر مورد انتظار با مسیر فعلی، آزمایش کنید.

از آنجایی که می خواهید اجرای برنامه بتن خود را آزمایش کنید، کلیک بر روی رابط کاربری ترجیح داده می شود. برای یادگیری نحوه آزمایش این مورد در کنار توابع منفرد قابل ترکیب به صورت مجزا، مطمئن شوید که Testing in Jetpack Compose Codelab را بررسی کنید.

همچنین می توانید از navController برای بررسی ادعاهای خود با مقایسه مسیر فعلی با مسیر مورد انتظار، با استفاده از navController 's currentBackStackEntry استفاده کنید:

@Test
fun appNavHost_clickAllProfiles_navigateToProfiles() {
    composeTestRule.onNodeWithContentDescription("All Profiles")
        .performScrollTo()
        .performClick()

    assertTrue(navController.currentBackStackEntry?.destination?.hasRoute<Profile>() ?: false)
}

برای راهنمایی بیشتر در مورد اصول اولیه تست Compose، به تست طرح‌بندی Compose خود و تست در لبه کد Jetpack Compose مراجعه کنید. برای کسب اطلاعات بیشتر در مورد تست پیشرفته کد پیمایش، از راهنمای Test Navigation دیدن کنید.

بیشتر بدانید

برای کسب اطلاعات بیشتر در مورد ناوبری Jetpack، به شروع کار با مؤلفه ناوبری مراجعه کنید یا از نوار کد ناوبری Jetpack Compose استفاده کنید.

برای یادگیری نحوه طراحی ناوبری برنامه خود به گونه ای که با اندازه های مختلف صفحه نمایش، جهت گیری ها و عوامل شکل سازگار شود، به ناوبری برای رابط های کاربری پاسخگو مراجعه کنید.

برای آشنایی با پیاده‌سازی مسیریابی Compose پیشرفته‌تر در یک برنامه مدولار شده، از جمله مفاهیمی مانند نمودارهای تودرتو و ادغام نوار پیمایش پایین، به برنامه Now in Android در GitHub نگاهی بیندازید.

نمونه ها

{% کلمه به کلمه %} {% آخر کلمه %} {% کلمه به کلمه %} {% آخر کلمه %}