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

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

برای جدیدترین کتابخانه ناوبری آلفا که به‌طور خاص برای Compose ساخته شده است، به مستندات Navigation 3 مراجعه کنید.

راه‌اندازی

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

شیار

dependencies {
    def nav_version = "2.9.5"

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

کاتلین

dependencies {
    val nav_version = "2.9.5"

    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 دیگری برای باز کردن برنامه خود در مقصد لینک عمیق استفاده کنید.

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

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

یک نوار ناوبری تطبیقی ​​در پایین و ریل ناوبری بسازید

NavigationSuiteScaffold رابط کاربری ناوبری مناسب را بر اساس WindowSizeClass که برنامه شما در آن رندر می‌شود، نمایش می‌دهد. در صفحه نمایش‌های جمع و جور، NavigationSuiteScaffold یک نوار ناوبری در پایین صفحه نمایش می‌دهد؛ در یک صفحه نمایش بزرگ، به جای آن یک ریل ناوبری نمایش داده می‌شود.

برای اطلاعات بیشتر به ساخت ناوبری تطبیقی ​​مراجعه کنید.

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

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

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

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

برای تغییر مقصدها درون کد 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 ای ارسال کنید و در عوض باید navigation callback ها را به عنوان پارامتر ارسال کنید. این کار باعث می‌شود که همه composable های شما به صورت جداگانه قابل تست باشند، زیرا در تست‌ها به نمونه‌ای از navController نیاز ندارند.

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

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

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

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

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

@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های شما و همچنین composableهای صفحه نمایش شخصی شما، پوشش دهند.

آزمایش NavHost

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

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

NavHost برنامه خود را در یک composable قرار دهید که یک NavHostController به عنوان پارامتر می‌پذیرد.

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

اکنون می‌توانید AppNavHost و تمام منطق ناوبری تعریف شده درون NavHost را با ارسال نمونه‌ای از مصنوع تست ناوبری TestNavHostController آزمایش کنید. یک تست رابط کاربری که مقصد شروع برنامه و 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 را بررسی کنید.

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

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

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

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

بیشتر بدانید

برای کسب اطلاعات بیشتر در مورد Jetpack Navigation، به بخش «شروع کار با کامپوننت Navigation» مراجعه کنید یا به آزمایشگاه کدنویسی Jetpack Compose Navigation مراجعه کنید.

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

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

نمونه‌ها

{% کلمه به کلمه %} {% فعل کمکی %} {% کلمه به کلمه %} {% فعل کمکی %}